Compare commits

...

77 Commits

Author SHA1 Message Date
Lovell Fuller
18fd6ef119 Release v0.18.3 2017-09-13 10:51:08 +01:00
Matt Parrish
0004f5d2ff Migrate from got to simple-get (#945)
The simple-get module provides support for basic auth and
is already used by the future prebuild dependency.
2017-09-12 07:38:17 +01:00
Mario Viens
5f29d1ba9c Clarifying cache documentation (#919) 2017-08-19 10:43:36 +01:00
Jasper Blues
791fd35c35 Update README.md (#913) 2017-08-16 12:09:02 +01:00
Kleis Auke Wolthuizen
e0d622d347 Skip shrink-on-load when trimming (#887) (#888) 2017-07-28 21:02:30 +01:00
Lovell Fuller
6b34e8a804 Docs: FreeBSD gcc v4/v5 needs _GLIBCXX_USE_C99 for C++11 #873 2017-07-25 14:54:44 +01:00
Lovell Fuller
eb8773fe3e Use detect-libc instead of ldd output parsing 2017-07-17 12:11:13 +01:00
Lovell Fuller
b40e3fa1f1 Docs: Alpine vips-dev package requires fftw-dev also 2017-07-15 14:47:25 +01:00
Lovell Fuller
d25d761b55 Update benchmark deps, allow node-images to fail 2017-07-14 13:18:13 +01:00
Lovell Fuller
d6051dd714 Release v0.18.2 2017-07-01 09:52:47 +01:00
Lovell Fuller
53ff061efa Document SHARP_DIST_BASE_URL #841 2017-07-01 09:44:02 +01:00
Lovell Fuller
72b0efd393 Remove possible switch case fall through
Luckily there were no side effects in this... case
2017-06-25 19:09:20 +01:00
Lovell Fuller
df97ef23d9 Document/changelog for Solus Linux support #857 2017-06-25 17:42:03 +01:00
Ekrem Karaca
f6373971bd Add support for Solus OS (#857) 2017-06-24 13:48:03 +01:00
Lovell Fuller
ec617f2489 Document minimum Node dependency of v4.5.0 #847 2017-06-20 22:05:05 +01:00
Lovell Fuller
502ae78579 Allow binary download URL override via SHARP_DIST_BASE_URL #841 2017-06-20 21:40:46 +01:00
Lovell Fuller
49297d6afb Ensure flip and flop operations work with auto-rotate #837 2017-06-19 23:42:26 +01:00
Lovell Fuller
29354badd8 Docs: add Alpine Linux install details 2017-06-19 08:13:49 +01:00
Lovell Fuller
3c4de796c8 Dependency bumps and next point release 2017-06-04 19:55:45 +01:00
Lovell Fuller
c7f4488e77 Docs and changelog entry for #828 2017-06-04 19:51:22 +01:00
Yves Bos
d8765f955d Allow xres and yres to be set for TIFF output (#828) 2017-06-03 10:52:09 +01:00
Lovell Fuller
9f20037dad Release v0.18.1 2017-05-30 21:02:35 +01:00
Lovell Fuller
2ebb090df2 Update Travis CI to Ubuntu 16.04
Rollback Appveyor CI from Node 8 to 7
2017-05-30 20:50:32 +01:00
Lovell Fuller
110fff3ab9 Replace Node 7 with Node 8 in CI environment 2017-05-30 20:32:15 +01:00
Lovell Fuller
f42a1ceab7 Recalculate residual after adjusting shrink #831 2017-05-30 20:22:15 +01:00
Lovell Fuller
9e39a7fa95 Correct shrink calc, regression introduced in e398b47 #831 2017-05-30 17:16:41 +01:00
Lovell Fuller
c879df3b31 Release v0.18.0 2017-05-30 08:09:59 +01:00
Lovell Fuller
361ed98353 Remove previously-deprecated output format 'option' functions 2017-05-23 21:57:05 +01:00
Lovell Fuller
d45f8ef2d3 Document the cache-free nature of metadata extraction #796 2017-05-23 21:24:29 +01:00
Lovell Fuller
d6a63d11d7 Docs refresh 2017-05-22 21:49:37 +01:00
jingsam
4c6804eadc Add toFormat 'jpg' alias for 'jpeg' (#814) 2017-05-22 12:59:43 +01:00
Nicolas Coden
99810c0311 Add support for any rotation angle (#791)
Allow to provide any positive or negative multiple of 90 to `.rotate(...)`.
Negative angles and angles above 360 are converted to valid 0/90/180/270
rotations (0 rotations are still ignored).

Changes:
- [Node] Add `useExifOrientation` internal variable to know if the Exif
  orientation must be used instead of the provided angle. This allows to save a
  negative angle in the `angle` option, because the `-1` special case is not
  needed.

- [Node] Change check for planed-rotation in extract, to prepare a
  rotation before extraction: check with both `angle` and `useExifOrientation`
  options.
  I think this check contains a bit too much logics on rotation options. Maybe
  we could move this condition to a dedicated function.

- [C++] Separate `CalculateRotationAndFlip` into two generic functions:
  - `CalculateExifRotationAndFlip`: Calculate the angle of rotation and
    need-to-flip for the given Exif orientation.
  - `CalculateAngleRotation`: Calculate the rotation for the given angle.

  One or the other function is used to calculate the rotation, depending on
  wether the Exif orientation tag or the provided angle must be used.

- Add unit tests for `-3690`, `-450`, `-90`, `90`, `450`, `3690` and `-3780`,
  `-540`, `0`, `180`, `540`, `3780` rotations
- Add `320x240` fixture image for tests.

Unrelated changes (squashed):
- Add ncoden to the list of contributors
2017-05-22 11:08:33 +01:00
gmaliar
d15fb1ab1b Docs: add link to TailorBrands-maintained libvips Dockerfiles (#813) 2017-05-21 20:37:57 +01:00
Lovell Fuller
0a6d8b37ad Ensure double to int cast introduced in 4d1a169 is static 2017-05-21 19:05:56 +01:00
Lovell Fuller
f78ffdb9ce Upgrade to libvips v8.5.5 2017-05-21 18:31:06 +01:00
Lovell Fuller
b7b6fdbdf5 Update perf test contenders, add node-images 2017-05-13 20:08:53 +01:00
Lovell Fuller
e398b471e1 Prevent aliasing by using dynamic values for shrink(-on-load) 2017-05-13 18:46:39 +01:00
Lovell Fuller
48f69f3d88 Upgrade libpng to v1.6.29 2017-05-13 18:20:54 +01:00
Lovell Fuller
95850d75f6 Include pixel format depth when reading metadata 2017-05-07 09:29:38 +01:00
Lovell Fuller
c41d755441 Ctor single arg: allow plain object, reject null/undefined
Thank you @kub1x
2017-05-06 19:03:14 +01:00
Lovell Fuller
39a21787b7 Remove 'require' test as bufferutil now ships prebuilt 2017-05-06 15:49:50 +01:00
Lovell Fuller
36078f9903 Switch to the libvips crop strategy implementations 2017-05-06 14:46:28 +01:00
Lovell Fuller
2f534dc01c Base maximum output dimensions on limitation of format 2017-05-04 23:20:37 +01:00
Lovell Fuller
c8e59f08ec Add support for Buffer and Stream-based TIFF output 2017-05-04 16:40:49 +01:00
Lovell Fuller
19dd6a997f Doc refresh, thank you @cspotcode 2017-05-01 09:34:10 +01:00
Lovell Fuller
4d1a1694cd Improve perf/accuracy of nearest neighbour integral upsample 2017-04-30 20:54:48 +01:00
Lovell Fuller
52bea15ad7 Upgrade libvips dependency to v8.5.4, plus other bumps 2017-04-26 23:04:08 +01:00
Lovell Fuller
6592361c5a Ensure ARM64 pre-built binaries use correct C++11 ABI 2017-04-26 21:41:03 +01:00
Lovell Fuller
f3f83494f5 Credit contributor YvesBos 2017-04-26 21:40:30 +01:00
Lovell Fuller
1169afbe90 Avoid (un)premultiplication for overlay image without alpha channel
Add 'premultiplied' boolean attribute to output info, helps test
2017-04-26 21:37:43 +01:00
Lovell Fuller
301bfbd271 Expose libvips warnings via NODE_DEBUG env var 2017-04-26 21:37:43 +01:00
Lovell Fuller
46aec7eabc Upgrade libvips dependency and packaging to v8.5.1 2017-04-26 21:37:43 +01:00
YvesBos
4cd3b66761 Add support for squashing TIFF output to 1-bit (#783) 2017-04-26 17:47:29 +01:00
Jakub Podlaha
567e3dd258 Add gentoo support to glibc detection (#760) 2017-04-06 15:17:30 +01:00
Lovell Fuller
fcf853712c Release v0.17.3 2017-04-01 10:20:44 +01:00
Lovell Fuller
088d36b47b Add support for TIFF float predictor 2017-04-01 10:08:47 +01:00
Lovell Fuller
27fb864ac4 Update dev deps, deconstify all the functions, API doc refresh 2017-03-31 21:42:23 +01:00
Lovell Fuller
4001c4a48a Add changelog and credit for #738 2017-03-31 21:17:19 +01:00
Sagiv Frankel
f64c18ef15 Docs: Add download info to Heroku section (#748) 2017-03-30 12:55:33 +01:00
Kristo Jorgenson
f8e72f443d Expose TIFF compression and predictor options (#738) 2017-03-29 12:12:04 +01:00
Lovell Fuller
5e015cc3ca Docs: use NODE_MODULES_CACHE=false for Heroku+yarn #722 2017-03-27 20:31:50 +01:00
Andreas Lind
9707f8c5d2 Add support for passing the crop strategy as a string (#735) 2017-03-16 14:27:09 +00:00
Lovell Fuller
6b1d698448 Add credit and changelog for #732 2017-03-16 07:37:05 +00:00
Alice Monday
72f69dda30 Add support for the "nearest" kernel for image reductions (#732) 2017-03-14 10:29:23 +00:00
Lovell Fuller
8b5d8a0577 Switch from seq to random access for normalise and 'smart' crop 2017-03-11 19:56:55 +00:00
Lovell Fuller
1aa053ce6f Create blank image (width, height, channels, background) #470 2017-03-11 11:46:01 +00:00
Lovell Fuller
701b1c4216 Document overlayWith image density parameter #729 2017-03-10 22:59:46 +00:00
Lovell Fuller
f1c4cef781 Version bump of dev dependencies 2017-03-04 22:28:01 +00:00
Lovell Fuller
6fe5b307b1 Allow toBuffer to resolve Promise with info+data #143 2017-03-04 22:15:31 +00:00
Lovell Fuller
679ce08998 Small doc updates 2017-03-04 18:37:23 +00:00
Lovell Fuller
eeb923eb5b Update docs domain name 2017-03-04 18:36:18 +00:00
Lovell Fuller
142c431745 Release v0.17.2 2017-02-11 10:35:34 +00:00
Lovell Fuller
81f5589411 Add use of 'cc' to improve C++ code style linting 2017-02-11 09:59:23 +00:00
Mark van Seventer
04f5c884a4 Add CLI tools section to installation guide (#691) 2017-01-25 21:34:23 +00:00
Lovell Fuller
d8df503404 Ensure Readable can start flowing after Writable finish #671 2017-01-22 14:03:06 +00:00
Lovell Fuller
d241efcdbe Add changelog entry and credit for #685 2017-01-22 13:58:11 +00:00
Rahul Nanwani
a1b8efe721 Expose WebP alpha quality, lossless and near-lossless output options (#685) 2017-01-19 13:45:32 +00:00
89 changed files with 2469 additions and 1327 deletions

View File

@@ -1,21 +1,27 @@
language: node_js language: node_js
node_js: matrix:
- "4" include:
- "6" - os: linux
- "7" dist: trusty
os: sudo: false
- linux node_js: "4"
- osx - os: linux
sudo: false dist: trusty
addons: sudo: false
apt: node_js: "6"
sources: - os: linux
- ubuntu-toolchain-r-test dist: trusty
packages: sudo: false
- g++-4.8 node_js: "8"
osx_image: xcode8 - os: osx
before_install: osx_image: xcode8
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi node_js: "4"
- os: osx
osx_image: xcode8
node_js: "6"
- os: osx
osx_image: xcode8
node_js: "8"
after_success: after_success:
- npm install coveralls - npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

View File

@@ -41,7 +41,6 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.17.0 | quill |
| v0.18.0 | ridge | | v0.18.0 | ridge |
| v0.19.0 | suit | | v0.19.0 | suit |
@@ -72,12 +71,7 @@ These can be converted to Markdown by running:
npm run docs npm run docs
``` ```
The `types.d.ts` TypeScript declaration can be generated by running: Please include documentation updates in any Pull Request that modifies the public API.
```sh
npm run types
```
Please include documentation and TypeScript declaration updates in any Pull Request that modifies the public API.
## Run the tests ## Run the tests

View File

@@ -30,8 +30,11 @@ import sharp from 'sharp';
sharp(inputBuffer) sharp(inputBuffer)
.resize(320, 240) .resize(320, 240)
.toFile('output.webp', (err, info) => ... ); .toFile('output.webp', (err, info) => ... );
// A Promises/A+ promise is returned when callback is not provided.
``` ```
```javascript ```javascript
sharp('input.jpg') sharp('input.jpg')
.rotate() .rotate()

View File

@@ -144,7 +144,7 @@
}], }],
['OS == "linux"', { ['OS == "linux"', {
'variables': { 'variables': {
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")' 'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
}, },
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=0'
@@ -267,7 +267,7 @@
'vendor/lib/libstdc++-6.dll', 'vendor/lib/libstdc++-6.dll',
'vendor/lib/libtiff-5.dll', 'vendor/lib/libtiff-5.dll',
'vendor/lib/libvips-42.dll', 'vendor/lib/libvips-42.dll',
'vendor/lib/libwebp-6.dll', 'vendor/lib/libwebp-7.dll',
'vendor/lib/libxml2-2.dll', 'vendor/lib/libxml2-2.dll',
'vendor/lib/zlib1.dll' 'vendor/lib/zlib1.dll'
] ]

View File

@@ -3,14 +3,14 @@
const fs = require('fs'); const fs = require('fs');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const zlib = require('zlib');
const caw = require('caw'); const caw = require('caw');
const got = require('got'); const simpleGet = require('simple-get');
const semver = require('semver'); const semver = require('semver');
const tar = require('tar'); const tar = require('tar');
const detectLibc = require('detect-libc');
const distBaseUrl = 'https://dl.bintray.com/lovell/sharp/'; const distBaseUrl = process.env.SHARP_DIST_BASE_URL || 'https://dl.bintray.com/lovell/sharp/';
// Use NPM-provided environment variable where available, falling back to require-based method for Electron // Use NPM-provided environment variable where available, falling back to require-based method for Electron
const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips; const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips;
@@ -29,21 +29,22 @@ const isFile = function (file) {
}; };
const unpack = function (tarPath, done) { const unpack = function (tarPath, done) {
const extractor = tar.Extract({ path: path.join(__dirname, 'vendor') }); const vendorPath = path.join(__dirname, 'vendor');
if (done) { fs.mkdirSync(vendorPath);
extractor.on('end', done); tar
} .extract({
extractor.on('error', error); file: tarPath,
fs.createReadStream(tarPath) cwd: vendorPath,
.on('error', error) strict: true
.pipe(zlib.Unzip()) })
.pipe(extractor); .then(done)
.catch(error);
}; };
const platformId = function () { const platformId = function () {
const platformId = [platform]; const platformId = [platform];
if (arch === 'arm' || arch === 'armhf' || arch === 'arch64') { if (arch === 'arm' || arch === 'armhf' || arch === 'arm64') {
const armVersion = (arch === 'arch64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6'; const armVersion = (arch === 'arm64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6';
platformId.push('armv' + armVersion); platformId.push('armv' + armVersion);
} else { } else {
platformId.push(arch); platformId.push(arch);
@@ -68,19 +69,15 @@ module.exports.download_vips = function () {
if (!isFile(vipsHeaderPath)) { if (!isFile(vipsHeaderPath)) {
// Ensure Intel 64-bit or ARM // Ensure Intel 64-bit or ARM
if (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 of libvips - please see http://sharp.dimens.io/page/install');
} }
// Ensure glibc >= 2.15 // Ensure glibc Linux
const lddVersion = process.env.LDD_VERSION; if (detectLibc.isNonGlibcLinux) {
if (lddVersion) { error(`Use with ${detectLibc.family} libc requires manual installation of libvips - please see http://sharp.dimens.io/page/install`);
if (/(glibc|gnu libc)/i.test(lddVersion)) { }
const glibcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : ''; // Ensure glibc >= 2.13
if (glibcVersion && semver.lt(glibcVersion + '.0', '2.13.0')) { if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) {
error('glibc version ' + glibcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/'); error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips - please see http://sharp.dimens.io/page/install`);
}
} else {
error(lddVersion.split(/\n/)[0] + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
} }
// Arch/platform-specific .tar.gz // Arch/platform-specific .tar.gz
const tarFilename = ['libvips', minimumLibvipsVersion, platformId()].join('-') + '.tar.gz'; const tarFilename = ['libvips', minimumLibvipsVersion, platformId()].join('-') + '.tar.gz';
@@ -98,19 +95,22 @@ module.exports.download_vips = function () {
} catch (err) {} } catch (err) {}
}); });
}); });
const gotOpt = { const url = distBaseUrl + tarFilename;
const simpleGetOpt = {
url: url,
agent: caw(null, { agent: caw(null, {
protocol: 'https' protocol: 'https'
}) })
}; };
const url = distBaseUrl + tarFilename; simpleGet(simpleGetOpt, function (err, response) {
got.stream(url, gotOpt).on('response', function (response) { if (err) {
error('Download of ' + url + ' failed: ' + err.message);
}
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
error(url + ' status code ' + response.statusCode); error(url + ' status code ' + response.statusCode);
} }
}).on('error', function (err) { response.pipe(tmpFile);
error('Download of ' + url + ' failed: ' + err.message); });
}).pipe(tmpFile);
} }
} }
}; };

View File

@@ -1,6 +1,6 @@
machine: machine:
node: node:
version: v4.6.1 version: v4.8.4
services: services:
- docker - docker
test: test:

View File

@@ -37,7 +37,7 @@ An alpha channel may be present, and will be unchanged by the operation.
**Parameters** **Parameters**
- `greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -47,7 +47,7 @@ Alternative spelling of `greyscale`.
**Parameters** **Parameters**
- `grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**

View File

@@ -11,19 +11,27 @@ Overlay (composite) an image over the processed (resized, extracted etc.) image.
The overlay image must be the same size or smaller than the processed image. The overlay image must be the same size or smaller than the processed image.
If both `top` and `left` options are provided, they take precedence over `gravity`. If both `top` and `left` options are provided, they take precedence over `gravity`.
If the overlay image contains an alpha channel then composition with premultiplication will occur.
**Parameters** **Parameters**
- `overlay` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file. - `overlay` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.gravity` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** gravity at which to place the overlay. (optional, default `'centre'`) - `options.gravity` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** gravity at which to place the overlay. (optional, default `'centre'`)
- `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the top edge. - `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the top edge.
- `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the left edge. - `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the left edge.
- `options.tile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`) - `options.tile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `options.cutout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`) - `options.cutout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`)
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** integral number representing the DPI for vector overlay image. (optional, default `72`)
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes overlay when using raw pixel data. - `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes overlay when using raw pixel data.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.create` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes a blank overlay to be created.
- `options.create.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.create.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.create.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 3-4
- `options.create.background` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))?** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
**Examples** **Examples**

View File

@@ -14,13 +14,18 @@
- `input` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))?** if present, can be - `input` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined. JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** if present, is an Object with optional attributes. - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** if present, is an Object with optional attributes.
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** integral number representing the DPI for vector images. (optional, default `72`) - `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** integral number representing the DPI for vector images. (optional, default `72`)
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel image data. See `raw()` for pixel ordering. - `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** - `options.raw.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 1-4
- `options.create` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes a new image to be created.
- `options.create.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.create.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.create.channels` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 3-4
- `options.create.background` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))?** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
**Examples** **Examples**
@@ -46,6 +51,21 @@ var transformer = sharp()
readableStream.pipe(transformer).pipe(writableStream); readableStream.pipe(transformer).pipe(writableStream);
``` ```
```javascript
// Create a blank 300x200 PNG image of semi-transluent red pixels
sharp({
create: {
width: 300,
height: 200,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 128 }
}
})
.png()
.toBuffer()
.then( ... );
```
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
Returns **[Sharp](#sharp)** Returns **[Sharp](#sharp)**
@@ -57,7 +77,7 @@ An Object containing nested boolean values representing the available input and
**Examples** **Examples**
```javascript ```javascript
console.log(sharp.format()); console.log(sharp.format);
``` ```
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**

View File

@@ -28,14 +28,15 @@ Returns **Sharp**
## metadata ## metadata
Fast access to image metadata without decoding any compressed image data. Fast access to (uncached) image metadata without decoding any compressed image data.
A Promises/A+ promise is returned when `callback` is not provided. A Promises/A+ promise is returned when `callback` is not provided.
- `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`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
- `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
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -88,6 +89,6 @@ This will reduce memory usage and can improve performance on some systems.
**Parameters** **Parameters**
- `sequentialRead` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `sequentialRead` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**

View File

@@ -24,7 +24,10 @@
Rotate the output image by either an explicit angle Rotate the output image by either an explicit angle
or auto-orient based on the EXIF `Orientation` tag. or auto-orient based on the EXIF `Orientation` tag.
Use this method without angle to determine the angle from EXIF data. If an angle is provided, it is converted to a valid 90/180/270deg rotation.
For example, `-450` will produce a 270deg rotation.
If no angle is provided, it is determined from the EXIF data.
Mirroring is supported and may infer the use of a flip operation. Mirroring is supported and may infer the use of a flip operation.
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -34,7 +37,7 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
**Parameters** **Parameters**
- `angle` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 0, 90, 180 or 270. (optional, default `auto`) - `angle` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** angle of rotation, must be a multiple of 90. (optional, default `auto`)
**Examples** **Examples**
@@ -101,7 +104,7 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters** **Parameters**
- `flip` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `flip` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -112,7 +115,7 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters** **Parameters**
- `flop` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `flop` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -126,8 +129,8 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
**Parameters** **Parameters**
- `sigma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `sigma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the level of sharpening to apply to "flat" areas. (optional, default `1.0`) - `flat` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`) - `jagged` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -184,7 +187,7 @@ Merge alpha transparency channel, if any, with `background`.
**Parameters** **Parameters**
- `flatten` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `flatten` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -194,7 +197,7 @@ Trim "boring" pixels from all edges that contain values within a percentage simi
**Parameters** **Parameters**
- `tolerance` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1 and 99 representing the percentage similarity. (optional, default `10`) - `tolerance` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** value between 1 and 99 representing the percentage similarity. (optional, default `10`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -211,7 +214,7 @@ when applying a gamma correction.
**Parameters** **Parameters**
- `gamma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1.0 and 3.0. (optional, default `2.2`) - `gamma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** value between 1.0 and 3.0. (optional, default `2.2`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -224,7 +227,7 @@ Produce the "negative" of the image.
**Parameters** **Parameters**
- `negate` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `negate` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -234,7 +237,7 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
**Parameters** **Parameters**
- `normalise` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `normalise` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -244,7 +247,7 @@ Alternative spelling of normalise.
**Parameters** **Parameters**
- `normalize` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `normalize` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**
@@ -258,8 +261,8 @@ Convolve the image with the specified kernel.
- `kernel.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. - `kernel.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels.
- `kernel.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. - `kernel.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels.
- `kernel.kernel` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Array of length `width*height` containing the kernel values. - `kernel.kernel` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)&lt;[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the scale of the kernel in pixels. (optional, default `sum`) - `kernel.scale` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the offset of the kernel in pixels. (optional, default `0`) - `kernel.offset` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the offset of the kernel in pixels. (optional, default `0`)
**Examples** **Examples**
@@ -287,10 +290,10 @@ Any pixel value greather than or equal to the threshold value will be set to 255
**Parameters** **Parameters**
- `threshold` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`) - `threshold` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** convert to single channel greyscale. (optional, default `true`) - `options.greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling for greyscale. (optional, default `true`) - `options.grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling for greyscale. (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters

View File

@@ -27,7 +27,8 @@ A Promises/A+ promise is returned when `callback` is not provided.
- `fileOut` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the path to write the image data to. - `fileOut` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the path to write the image data to.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called on completion with two arguments `(err, info)`. - `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -37,18 +38,21 @@ Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe
## toBuffer ## toBuffer
Write output to a Buffer. Write output to a Buffer.
JPEG, PNG, WebP, and RAW output are supported. JPEG, PNG, WebP, TIFF and RAW output are supported.
By default, the format will match the input image, except GIF and SVG input which become PNG output. By default, the format will match the input image, except GIF and SVG input which become PNG output.
`callback`, if present, gets three arguments `(err, buffer, info)` where: `callback`, if present, gets three arguments `(err, data, info)` where:
- `err` is an error message, if any. - `err` is an error, if any.
- `buffer` is the output image data. - `data` is the output image data.
- `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
A Promises/A+ promise is returned when `callback` is not provided. `channels` and `premultiplied` (indicating if premultiplication was used).
A Promise is returned when `callback` is not provided.
**Parameters** **Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.resolveWithObject` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** - `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?**
Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Buffer](https://nodejs.org/api/buffer.html)>** when no callback is provided Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Buffer](https://nodejs.org/api/buffer.html)>** when no callback is provided
@@ -76,14 +80,14 @@ Use these JPEG options for output image.
**Parameters** **Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply trellis quantisation, requires mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** apply trellis quantisation, requires mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply overshoot deringing, requires mozjpeg (optional, default `false`) - `options.overshootDeringing` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** apply overshoot deringing, requires mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`) - `options.optimiseScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of optimiseScans (optional, default `false`) - `options.optimizeScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling of optimiseScans (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force JPEG output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -97,10 +101,10 @@ Use these PNG options for output image.
**Parameters** **Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** zlib compression level (optional, default `6`) - `options.compressionLevel` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zlib compression level (optional, default `6`)
- `options.adaptiveFiltering` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use adaptive row filtering (optional, default `true`) - `options.adaptiveFiltering` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use adaptive row filtering (optional, default `true`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force PNG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force PNG output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -114,8 +118,11 @@ Use these WebP options for output image.
**Parameters** **Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force WebP output, otherwise attempt to use input format (optional, default `true`) - `options.alphaQuality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use near_lossless compression mode (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force WebP output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -129,8 +136,13 @@ Use these TIFF options for output image.
**Parameters** **Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** compression options: lzw, deflate, jpeg (optional, default `'jpeg'`)
- `options.predictor` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** compression predictor options: none, horizontal, float (optional, default `'none'`)
- `options.xres` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** squash 8-bit images down to 1 bit (optional, default `false`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options - Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -166,10 +178,10 @@ Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed arc
**Parameters** **Parameters**
- `tile` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `tile` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `tile.size` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `tile.size` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) - `tile.overlap` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.container` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `tile.container` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) - `tile.layout` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
**Examples** **Examples**

View File

@@ -17,6 +17,7 @@ By default, the resized image is centre cropped to the exact size specified.
Possible reduction kernels are: Possible reduction kernels are:
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
@@ -32,13 +33,13 @@ Possible enlargement interpolators are:
**Parameters** **Parameters**
- `width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. - `width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. - `height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.kernel` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the kernel to use for image reduction. (optional, default `'lanczos3'`) - `options.kernel` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.interpolator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the interpolator to use for image enlargement. (optional, default `'bicubic'`) - `options.interpolator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the interpolator to use for image enlargement. (optional, default `'bicubic'`)
- `options.centreSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`) - `options.centreSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`)
- `options.centerSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of centreSampling. (optional, default `false`) - `options.centerSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling of centreSampling. (optional, default `false`)
**Examples** **Examples**
@@ -77,7 +78,7 @@ then repeatedly ranks edge regions, discarding the edge with the lowest score ba
**Parameters** **Parameters**
- `crop` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`) - `crop` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`)
**Examples** **Examples**
@@ -171,6 +172,6 @@ This is equivalent to GraphicsMagick's `>` geometry option:
**Parameters** **Parameters**
- `withoutEnlargement` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`) - `withoutEnlargement` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp** Returns **Sharp**

View File

@@ -9,17 +9,17 @@
## cache ## cache
Gets, or when options are provided sets, the limits of _libvips'_ operation cache. Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
Existing entries in the cache will be trimmed after any change in limits. Existing entries in the cache will be trimmed after any change in limits.
This method always returns cache statistics, This method always returns cache statistics,
useful for determining how much working memory is required for a particular task. useful for determining how much working memory is required for a particular task.
**Parameters** **Parameters**
- `options` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching. - `options` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching (optional, default `true`)
- `options.memory` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum memory in MB to use for this cache (optional, default `50`) - `options.memory` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum number of files to hold open (optional, default `20`) - `options.files` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum number of operations to cache (optional, default `100`) - `options.items` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum number of operations to cache (optional, default `100`)
**Examples** **Examples**
@@ -37,7 +37,7 @@ Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer
## concurrency ## concurrency
Gets, or when a concurrency is provided sets, Gets or, when a concurrency is provided, sets
the number of threads _libvips'_ should create to process each image. the number of threads _libvips'_ should create to process each image.
The default value is the number of CPU cores. The default value is the number of CPU cores.
A value of `0` will reset to this default. A value of `0` will reset to this default.
@@ -89,7 +89,7 @@ Versions of liborc prior to 0.4.25 are known to segfault under heavy load.
**Parameters** **Parameters**
- `simd` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `false`) - `simd` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `false`)
**Examples** **Examples**

View File

@@ -1,9 +1,132 @@
# Changelog # Changelog
### v0.18 - "*ridge*"
Requires libvips v8.5.5.
#### v0.18.3 - 13<sup>th</sup> September 2017
* Skip shrink-on-load when trimming.
[#888](https://github.com/lovell/sharp/pull/888)
[@kleisauke](https://github.com/kleisauke)
* Migrate from got to simple-get for basic auth support.
[#945](https://github.com/lovell/sharp/pull/945)
[@pbomb](https://github.com/pbomb)
#### v0.18.2 - 1<sup>st</sup> July 2017
* Expose libvips' xres and yres properties for TIFF output.
[#828](https://github.com/lovell/sharp/pull/828)
[@YvesBos](https://github.com/YvesBos)
* Ensure flip and flop operations work with auto-rotate.
[#837](https://github.com/lovell/sharp/issues/837)
[@rexxars](https://github.com/rexxars)
* Allow binary download URL override via SHARP_DIST_BASE_URL env variable.
[#841](https://github.com/lovell/sharp/issues/841)
* Add support for Solus Linux.
[#857](https://github.com/lovell/sharp/pull/857)
[@ekremkaraca](https://github.com/ekremkaraca)
#### v0.18.1 - 30<sup>th</sup> May 2017
* Remove regression from #781 that could cause incorrect shrink calculation.
[#831](https://github.com/lovell/sharp/issues/831)
[@suprMax](https://github.com/suprMax)
#### v0.18.0 - 30<sup>th</sup> May 2017
* Remove the previously-deprecated output format "option" functions:
quality, progressive, compressionLevel, withoutAdaptiveFiltering,
withoutChromaSubsampling, trellisQuantisation, trellisQuantization,
overshootDeringing, optimiseScans and optimizeScans.
* Ensure maximum output dimensions are based on the format to be used.
[#176](https://github.com/lovell/sharp/issues/176)
[@stephanebachelier](https://github.com/stephanebachelier)
* Avoid costly (un)premultiply when using overlayWith without alpha channel.
[#573](https://github.com/lovell/sharp/issues/573)
[@strarsis](https://github.com/strarsis)
* Include pixel depth (e.g. "uchar") when reading metadata.
[#577](https://github.com/lovell/sharp/issues/577)
[@moedusa](https://github.com/moedusa)
* Add support for Buffer and Stream-based TIFF output.
[#587](https://github.com/lovell/sharp/issues/587)
[@strarsis](https://github.com/strarsis)
* Expose warnings from libvips via NODE_DEBUG=sharp environment variable.
[#607](https://github.com/lovell/sharp/issues/607)
[@puzrin](https://github.com/puzrin)
* Switch to the libvips implementation of "attention" and "entropy" crop strategies.
[#727](https://github.com/lovell/sharp/issues/727)
* Improve performance and accuracy of nearest neighbour integral upsampling.
[#752](https://github.com/lovell/sharp/issues/752)
[@MrIbby](https://github.com/MrIbby)
* Constructor single argument API: allow plain object, reject null/undefined.
[#768](https://github.com/lovell/sharp/issues/768)
[@kub1x](https://github.com/kub1x)
* Ensure ARM64 pre-built binaries use correct C++11 ABI version.
[#772](https://github.com/lovell/sharp/issues/772)
[@ajiratech2](https://github.com/ajiratech2)
* Prevent aliasing by using dynamic values for shrink(-on-load).
[#781](https://github.com/lovell/sharp/issues/781)
[@kleisauke](https://github.com/kleisauke)
* Expose libvips' "squash" parameter to enable 1-bit TIFF output.
[#783](https://github.com/lovell/sharp/pull/783)
[@YvesBos](https://github.com/YvesBos)
* Add support for rotation using any multiple of +/-90 degrees.
[#791](https://github.com/lovell/sharp/pull/791)
[@ncoden](https://github.com/ncoden)
* Add "jpg" alias to toFormat as shortened form of "jpeg".
[#814](https://github.com/lovell/sharp/pull/814)
[@jingsam](https://github.com/jingsam)
### v0.17 - "*quill*" ### v0.17 - "*quill*"
Requires libvips v8.4.2. Requires libvips v8.4.2.
#### v0.17.3 - 1<sup>st</sup> April 2017
* Allow toBuffer to optionally resolve a Promise with both info and data.
[#143](https://github.com/lovell/sharp/issues/143)
[@salzhrani](https://github.com/salzhrani)
* Create blank image of given width, height, channels and background.
[#470](https://github.com/lovell/sharp/issues/470)
[@pjarts](https://github.com/pjarts)
* Add support for the "nearest" kernel for image reductions.
[#732](https://github.com/lovell/sharp/pull/732)
[@alice0meta](https://github.com/alice0meta)
* Add support for TIFF compression and predictor options.
[#738](https://github.com/lovell/sharp/pull/738)
[@kristojorg](https://github.com/kristojorg)
#### v0.17.2 - 11<sup>th</sup> February 2017
* Ensure Readable side of Stream can start flowing after Writable side has finished.
[#671](https://github.com/lovell/sharp/issues/671)
[@danhaller](https://github.com/danhaller)
* Expose WebP alpha quality, lossless and near-lossless output options.
[#685](https://github.com/lovell/sharp/pull/685)
[@rnanwani](https://github.com/rnanwani)
#### v0.17.1 - 15<sup>th</sup> January 2017 #### v0.17.1 - 15<sup>th</sup> January 2017
* Improve error messages for invalid parameters. * Improve error messages for invalid parameters.

View File

@@ -22,7 +22,7 @@ the installation of any external runtime dependencies.
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images. This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data. Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output. Streams, Buffer objects and the filesystem can be used for input and output.
@@ -97,6 +97,11 @@ the help and code contributions of the following people:
* [Matthias Thoemmes](https://github.com/cmtt) * [Matthias Thoemmes](https://github.com/cmtt)
* [Patrick Paskaris](https://github.com/ppaskaris) * [Patrick Paskaris](https://github.com/ppaskaris)
* [Jérémy Lal](https://github.com/kapouer) * [Jérémy Lal](https://github.com/kapouer)
* [Alice Monday](https://github.com/alice0meta)
* [Kristo Jorgenson](https://github.com/kristojorg)
* [Yves Bos](https://github.com/YvesBos)
* [Nicolas Coden](https://github.com/ncoden)
* [Matt Parrish](https://github.com/pbomb)
Thank you! Thank you!

View File

@@ -10,9 +10,9 @@ yarn add sharp
### Prerequisites ### Prerequisites
* Node v4+ * Node v4.5.0+
* 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) and its dependencies * [node-gyp](https://github.com/TooTallNate/node-gyp#installation) and its dependencies (includes Python)
### Linux ### Linux
@@ -20,18 +20,19 @@ yarn add 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/vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.5MB. This involves an automated HTTPS download of approximately 7MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.: Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8 * Debian 7, 8
* Ubuntu 12.04, 14.04, 16.04 * Ubuntu 12.04, 14.04, 16.04
* Centos 7 * Centos 7
* Fedora 23, 24 * Fedora
* openSUSE 13.2 * openSUSE 13.2
* Archlinux * Archlinux
* Raspbian Jessie * Raspbian Jessie
* Amazon Linux 2016.03, 2016.09 * Amazon Linux 2016.03, 2016.09
* Solus
To use a globally-installed version of libvips instead of the provided binaries, To use a globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file make sure it is at least the version listed under `config.libvips` in the `package.json` file
@@ -46,10 +47,19 @@ This allows the use of newer versions of libvips with older versions of sharp.
For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6, For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6,
it is recommended to install a system-wide installation of libvips from source: it is recommended to install a system-wide installation of libvips from source:
https://github.com/jcupitt/libvips#building-libvips-from-a-source-tarball https://jcupitt.github.io/libvips/install.html#building-libvips-from-a-source-tarball
For Linux-based operating systems such as Alpine that use musl libc, #### Alpine Linux
the smaller stack size means libvips' cache should be disabled
libvips is available in the
[testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh
apk add vips-dev fftw-dev --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/
```
The smaller stack size of musl libc means
libvips may need to be used without a cache
via `sharp.cache(false)` to avoid a stack overflow. via `sharp.cache(false)` to avoid a stack overflow.
### Mac OS ### Mac OS
@@ -57,7 +67,7 @@ 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 and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.3MB. This involves an automated HTTPS download of approximately 7MB.
To use your own version of libvips instead of the provided binaries, make sure it is To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file and at least the version listed under `config.libvips` in the `package.json` file and
@@ -68,7 +78,7 @@ that it can be located using `pkg-config --modversion vips-cpp`.
[![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)
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 9MB. This involves an automated HTTPS download of approximately 11MB.
Only 64-bit (x64) `node.exe` is supported. Only 64-bit (x64) `node.exe` is supported.
@@ -81,11 +91,16 @@ This can be achieved via [FreshPorts](https://www.freshports.org/graphics/vips/)
cd /usr/ports/graphics/vips/ && make install clean cd /usr/ports/graphics/vips/ && make install clean
``` ```
FreeBSD's gcc v4 and v5 need `CXXFLAGS=-D_GLIBCXX_USE_C99` set for C++11 support due to
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
### Heroku ### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
[Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) This involves an automated HTTPS download of approximately 7MB.
and its dependencies.
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager.
### Docker ### Docker
@@ -103,6 +118,13 @@ docker pull marcbachmann/libvips
docker pull wjordan/libvips docker pull wjordan/libvips
``` ```
[Tailor Brands](https://github.com/TailorBrands) maintain
[Debian-based Dockerfiles for libvips and nodejs](https://github.com/TailorBrands/docker-libvips).
```sh
docker pull tailor/docker-libvips
```
### AWS Lambda ### AWS Lambda
In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp
@@ -135,6 +157,10 @@ You can now download your deployment ZIP using `scp` and upload it to Lambda. Be
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive) * [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp) * [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
### CLI tools
* [sharp-cli](https://www.npmjs.com/package/sharp-cli)
### Security ### Security
Many users of this module process untrusted, user-supplied images, Many users of this module process untrusted, user-supplied images,
@@ -170,16 +196,27 @@ configuration file to prevent the use of coders known to be vulnerable.
Set the `MAGICK_CONFIGURE_PATH` environment variable Set the `MAGICK_CONFIGURE_PATH` environment variable
to the directory containing the `policy.xml` file. to the directory containing the `policy.xml` file.
### Licences ### Pre-compiled libvips binaries
If a global installation of libvips that meets the If a global installation of libvips that meets the
minimum version requirement cannot be found, minimum version requirement cannot be found,
this module will download a pre-compiled bundle of libvips this module will attempt to download a pre-compiled bundle of libvips
and its dependencies on Linux and Windows machines. and its dependencies on Linux and Windows machines.
Should you need to manually download and inspect these files, Should you need to manually download and inspect these files,
you can do so via https://dl.bintray.com/lovell/sharp/ you can do so via https://dl.bintray.com/lovell/sharp/
Should you wish to install these from your own location,
set the `SHARP_DIST_BASE_URL` environment variable, e.g.
```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
### Licences
This module is licensed under the terms of the This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE). [Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
@@ -193,6 +230,7 @@ Use of libraries under the terms of the LGPLv3 is via the
| Library | Used under the terms of | | Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------| |---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 | | cairo | Mozilla Public License 2.0 |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) | | fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) |
| freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) | | freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| giflib | MIT Licence | | giflib | MIT Licence |

View File

@@ -27,7 +27,7 @@ const bool = {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid channel * @throws {Error} Invalid channel
*/ */
const extractChannel = function extractChannel (channel) { function extractChannel (channel) {
if (channel === 'red') { if (channel === 'red') {
channel = 0; channel = 0;
} else if (channel === 'green') { } else if (channel === 'green') {
@@ -41,7 +41,7 @@ const extractChannel = function extractChannel (channel) {
throw new Error('Cannot extract invalid channel ' + channel); throw new Error('Cannot extract invalid channel ' + channel);
} }
return this; return this;
}; }
/** /**
* Join one or more channels to the image. * Join one or more channels to the image.
@@ -59,7 +59,7 @@ const extractChannel = function extractChannel (channel) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const joinChannel = function joinChannel (images, options) { function joinChannel (images, options) {
if (Array.isArray(images)) { if (Array.isArray(images)) {
images.forEach(function (image) { images.forEach(function (image) {
this.options.joinChannelIn.push(this._createInputDescriptor(image, options)); this.options.joinChannelIn.push(this._createInputDescriptor(image, options));
@@ -68,7 +68,7 @@ const joinChannel = function joinChannel (images, options) {
this.options.joinChannelIn.push(this._createInputDescriptor(images, options)); this.options.joinChannelIn.push(this._createInputDescriptor(images, options));
} }
return this; return this;
}; }
/** /**
* 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.
@@ -86,14 +86,14 @@ const joinChannel = function joinChannel (images, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const bandbool = function bandbool (boolOp) { function bandbool (boolOp) {
if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) { if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) {
this.options.bandBoolOp = boolOp; this.options.bandBoolOp = boolOp;
} else { } else {
throw new Error('Invalid bandbool operation ' + boolOp); throw new Error('Invalid bandbool operation ' + boolOp);
} }
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with channel-related functions. * Decorate the Sharp prototype with channel-related functions.

View File

@@ -27,7 +27,7 @@ const colourspace = {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameter * @throws {Error} Invalid parameter
*/ */
const background = function background (rgba) { function background (rgba) {
const colour = color(rgba); const colour = color(rgba);
this.options.background = [ this.options.background = [
colour.red(), colour.red(),
@@ -36,7 +36,7 @@ const background = function background (rgba) {
Math.round(colour.alpha() * 255) Math.round(colour.alpha() * 255)
]; ];
return this; return this;
}; }
/** /**
* Convert to 8-bit greyscale; 256 shades of grey. * Convert to 8-bit greyscale; 256 shades of grey.
@@ -48,19 +48,19 @@ const background = function background (rgba) {
* @param {Boolean} [greyscale=true] * @param {Boolean} [greyscale=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const greyscale = function greyscale (greyscale) { function greyscale (greyscale) {
this.options.greyscale = is.bool(greyscale) ? greyscale : true; this.options.greyscale = is.bool(greyscale) ? greyscale : true;
return this; return this;
}; }
/** /**
* Alternative spelling of `greyscale`. * Alternative spelling of `greyscale`.
* @param {Boolean} [grayscale=true] * @param {Boolean} [grayscale=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const grayscale = function grayscale (grayscale) { function grayscale (grayscale) {
return this.greyscale(grayscale); return this.greyscale(grayscale);
}; }
/** /**
* Set the output colourspace. * Set the output colourspace.
@@ -69,13 +69,13 @@ const grayscale = function grayscale (grayscale) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const toColourspace = function toColourspace (colourspace) { function toColourspace (colourspace) {
if (!is.string(colourspace)) { if (!is.string(colourspace)) {
throw new Error('Invalid output colourspace ' + colourspace); throw new Error('Invalid output colourspace ' + colourspace);
} }
this.options.colourspace = colourspace; this.options.colourspace = colourspace;
return this; return this;
}; }
/** /**
* Alternative spelling of `toColourspace`. * Alternative spelling of `toColourspace`.
@@ -83,9 +83,9 @@ const toColourspace = function toColourspace (colourspace) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const toColorspace = function toColorspace (colorspace) { function toColorspace (colorspace) {
return this.toColourspace(colorspace); return this.toColourspace(colorspace);
}; }
/** /**
* Decorate the Sharp prototype with colour-related functions. * Decorate the Sharp prototype with colour-related functions.

View File

@@ -8,6 +8,8 @@ const is = require('./is');
* The overlay image must be the same size or smaller than the processed image. * The overlay image must be the same size or smaller than the processed image.
* If both `top` and `left` options are provided, they take precedence over `gravity`. * If both `top` and `left` options are provided, they take precedence over `gravity`.
* *
* If the overlay image contains an alpha channel then composition with premultiplication will occur.
*
* @example * @example
* sharp('input.png') * sharp('input.png')
* .rotate(180) * .rotate(180)
@@ -33,14 +35,20 @@ const is = require('./is');
* @param {Number} [options.left] - the pixel offset from the left edge. * @param {Number} [options.left] - the pixel offset from the left edge.
* @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`. * @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
* @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. * @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another.
* @param {Number} [options.density=72] - integral number representing the DPI for vector overlay image.
* @param {Object} [options.raw] - describes overlay when using raw pixel data. * @param {Object} [options.raw] - describes overlay when using raw pixel data.
* @param {Number} [options.raw.width] * @param {Number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels] * @param {Number} [options.raw.channels]
* @param {Object} [options.create] - describes a blank overlay to be created.
* @param {Number} [options.create.width]
* @param {Number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const overlayWith = function overlayWith (overlay, options) { function overlayWith (overlay, options) {
this.options.overlay = this._createInputDescriptor(overlay, options, { this.options.overlay = this._createInputDescriptor(overlay, options, {
allowStream: false allowStream: false
}); });
@@ -60,10 +68,7 @@ const overlayWith = function overlayWith (overlay, options) {
} }
} }
if (is.defined(options.left) || is.defined(options.top)) { if (is.defined(options.left) || is.defined(options.top)) {
if ( if (is.integer(options.left) && options.left >= 0 && is.integer(options.top) && options.top >= 0) {
is.integer(options.left) && is.inRange(options.left, 0, this.constructor.maximum.width) &&
is.integer(options.top) && is.inRange(options.top, 0, this.constructor.maximum.height)
) {
this.options.overlayXOffset = options.left; this.options.overlayXOffset = options.left;
this.options.overlayYOffset = options.top; this.options.overlayYOffset = options.top;
} else { } else {
@@ -81,7 +86,7 @@ const overlayWith = function overlayWith (overlay, options) {
} }
} }
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with composite-related functions. * Decorate the Sharp prototype with composite-related functions.

View File

@@ -5,6 +5,7 @@ const util = require('util');
const stream = require('stream'); const stream = require('stream');
const events = require('events'); const events = require('events');
const semver = require('semver'); const semver = require('semver');
const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
// Versioning // Versioning
@@ -24,12 +25,15 @@ let versions = {
} catch (err) {} } catch (err) {}
})(); })();
// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');
/** /**
* @class Sharp * @class Sharp
* *
* Constructor factory to create an instance of `sharp`, to which further methods are chained. * Constructor factory to create an instance of `sharp`, to which further methods are chained.
* *
* JPEG, PNG or WebP format image data can be streamed out from this object. * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event. * When using Stream based output, derived attributes are available from the `info` event.
* *
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
@@ -54,20 +58,42 @@ let versions = {
* }); * });
* readableStream.pipe(transformer).pipe(writableStream); * readableStream.pipe(transformer).pipe(writableStream);
* *
* @example
* // Create a blank 300x200 PNG image of semi-transluent red pixels
* sharp({
* create: {
* width: 300,
* height: 200,
* channels: 4,
* background: { r: 255, g: 0, b: 0, alpha: 128 }
* }
* })
* .png()
* .toBuffer()
* .then( ... );
*
* @param {(Buffer|String)} [input] - if present, can be * @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Number} [options.density=72] - integral number representing the DPI for vector images. * @param {Number} [options.density=72] - integral number representing the DPI for vector images.
* @param {Object} [options.raw] - describes raw pixel image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width] * @param {Number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels] * @param {Number} [options.raw.channels] - 1-4
* @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width]
* @param {Number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const Sharp = function (input, options) { const Sharp = function (input, options) {
if (arguments.length === 1 && !is.defined(input)) {
throw new Error('Invalid input');
}
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input, options); return new Sharp(input, options);
} }
@@ -75,7 +101,7 @@ const Sharp = function (input, options) {
this.options = { this.options = {
// input options // input options
sequentialRead: false, sequentialRead: false,
limitInputPixels: maximum.pixels, limitInputPixels: Math.pow(0x3FFF, 2),
// ICC profiles // ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep, iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options // resize options
@@ -91,6 +117,7 @@ const Sharp = function (input, options) {
height: -1, height: -1,
canvas: 'crop', canvas: 'crop',
crop: 0, crop: 0,
useExifOrientation: false,
angle: 0, angle: 0,
rotateBeforePreExtract: false, rotateBeforePreExtract: false,
flip: false, flip: false,
@@ -134,6 +161,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,
jpegProgressive: false, jpegProgressive: false,
@@ -145,9 +173,19 @@ const Sharp = function (input, options) {
pngCompressionLevel: 6, pngCompressionLevel: 6,
pngAdaptiveFiltering: true, pngAdaptiveFiltering: true,
webpQuality: 80, webpQuality: 80,
webpAlphaQuality: 100,
webpLossless: false,
webpNearLossless: false,
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'none',
tiffSquash: false,
tiffXres: 1.0,
tiffYres: 1.0,
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
// Function to notify of libvips warnings
debuglog: debuglog,
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener: function (queueLength) {
queue.emit('change', queueLength); queue.emit('change', queueLength);
@@ -158,18 +196,6 @@ const Sharp = function (input, options) {
}; };
util.inherits(Sharp, stream.Duplex); util.inherits(Sharp, stream.Duplex);
/**
* Pixel limits.
* @member
* @private
*/
const maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
Sharp.maximum = maximum;
/** /**
* An EventEmitter that emits a `change` event when a task is either: * An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread * - queued, waiting for _libuv_ to provide a worker thread
@@ -186,7 +212,7 @@ Sharp.queue = queue;
/** /**
* An Object containing nested boolean values representing the available input and output formats/methods. * An Object containing nested boolean values representing the available input and output formats/methods.
* @example * @example
* console.log(sharp.format()); * console.log(sharp.format);
* @returns {Object} * @returns {Object}
*/ */
Sharp.format = sharp.format(); Sharp.format = sharp.format();

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const util = require('util'); const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
@@ -8,7 +8,7 @@ const sharp = require('../build/Release/sharp.node');
* Create Object containing input and input-related options. * Create Object containing input and input-related options.
* @private * @private
*/ */
const _createInputDescriptor = function _createInputDescriptor (input, inputOptions, containerOptions) { function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = {}; const inputDescriptor = {};
if (is.string(input)) { if (is.string(input)) {
// filesystem // filesystem
@@ -16,6 +16,9 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
} else if (is.buffer(input)) { } else if (is.buffer(input)) {
// Buffer // Buffer
inputDescriptor.buffer = input; inputDescriptor.buffer = input;
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
@@ -35,8 +38,8 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
if (is.defined(inputOptions.raw)) { if (is.defined(inputOptions.raw)) {
if ( if (
is.object(inputOptions.raw) && is.object(inputOptions.raw) &&
is.integer(inputOptions.raw.width) && is.inRange(inputOptions.raw.width, 1, this.constructor.maximum.width) && is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
is.integer(inputOptions.raw.height) && is.inRange(inputOptions.raw.height, 1, this.constructor.maximum.height) && is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4) is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
) { ) {
inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
@@ -46,11 +49,35 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }
} }
// Create new image
if (is.defined(inputOptions.create)) {
if (
is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
is.defined(inputOptions.create.background)
) {
inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels;
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
delete inputDescriptor.buffer;
} else {
throw new Error('Expected width, height, channels and background to create a new input image');
}
}
} else if (is.defined(inputOptions)) { } else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions); throw new Error('Invalid input options ' + inputOptions);
} }
return inputDescriptor; return inputDescriptor;
}; }
/** /**
* Handle incoming Buffer chunk on Writable Stream. * Handle incoming Buffer chunk on Writable Stream.
@@ -59,11 +86,17 @@ const _createInputDescriptor = function _createInputDescriptor (input, inputOpti
* @param {String} encoding - unused * @param {String} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
const _write = function _write (chunk, encoding, callback) { function _write (chunk, encoding, callback) {
/* istanbul ignore else */ /* istanbul ignore else */
if (Array.isArray(this.options.input.buffer)) { if (Array.isArray(this.options.input.buffer)) {
/* istanbul ignore else */ /* istanbul ignore else */
if (is.buffer(chunk)) { if (is.buffer(chunk)) {
if (this.options.input.buffer.length === 0) {
const that = this;
this.on('finish', function () {
that.streamInFinished = true;
});
}
this.options.input.buffer.push(chunk); this.options.input.buffer.push(chunk);
callback(); callback();
} else { } else {
@@ -72,26 +105,26 @@ const _write = function _write (chunk, encoding, callback) {
} else { } else {
callback(new Error('Unexpected data on Writable Stream')); callback(new Error('Unexpected data on Writable Stream'));
} }
}; }
/** /**
* Flattens the array of chunks accumulated in input.buffer. * Flattens the array of chunks accumulated in input.buffer.
* @private * @private
*/ */
const _flattenBufferIn = function _flattenBufferIn () { function _flattenBufferIn () {
if (this._isStreamInput()) { if (this._isStreamInput()) {
this.options.input.buffer = Buffer.concat(this.options.input.buffer); this.options.input.buffer = Buffer.concat(this.options.input.buffer);
} }
}; }
/** /**
* Are we expecting Stream-based input? * Are we expecting Stream-based input?
* @private * @private
* @returns {Boolean} * @returns {Boolean}
*/ */
const _isStreamInput = function _isStreamInput () { function _isStreamInput () {
return Array.isArray(this.options.input.buffer); return Array.isArray(this.options.input.buffer);
}; }
/** /**
* Take a "snapshot" of the Sharp instance, returning a new instance. * Take a "snapshot" of the Sharp instance, returning a new instance.
@@ -108,11 +141,11 @@ const _isStreamInput = function _isStreamInput () {
* *
* @returns {Sharp} * @returns {Sharp}
*/ */
const clone = function clone () { function clone () {
const that = this; const that = this;
// Clone existing options // Clone existing options
const clone = this.constructor.call(); const clone = this.constructor.call();
util._extend(clone.options, this.options); clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input // Pass 'finish' event to clone for Stream-based input
this.on('finish', function () { this.on('finish', function () {
// Clone inherits input data // Clone inherits input data
@@ -121,17 +154,18 @@ const clone = function clone () {
clone.emit('finish'); clone.emit('finish');
}); });
return clone; return clone;
}; }
/** /**
* Fast access to image metadata without decoding any compressed image data. * Fast access to (uncached) image metadata without decoding any compressed image data.
* A Promises/A+ promise is returned when `callback` is not provided. * A Promises/A+ promise is returned when `callback` is not provided.
* *
* - `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`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
* - `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
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -156,7 +190,7 @@ const clone = function clone () {
* @param {Function} [callback] - called with the arguments `(err, metadata)` * @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp} * @returns {Promise<Object>|Sharp}
*/ */
const metadata = function metadata (callback) { function metadata (callback) {
const that = this; const that = this;
if (is.fn(callback)) { if (is.fn(callback)) {
if (this._isStreamInput()) { if (this._isStreamInput()) {
@@ -194,7 +228,7 @@ const metadata = function metadata (callback) {
}); });
} }
} }
}; }
/** /**
* Do not process input images where the number of pixels (width * height) exceeds this limit. * Do not process input images where the number of pixels (width * height) exceeds this limit.
@@ -204,20 +238,20 @@ const metadata = function metadata (callback) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid limit * @throws {Error} Invalid limit
*/ */
const limitInputPixels = function limitInputPixels (limit) { function limitInputPixels (limit) {
// if we pass in false we represent the integer as 0 to disable // if we pass in false we represent the integer as 0 to disable
if (limit === false) { if (limit === false) {
limit = 0; limit = 0;
} else if (limit === true) { } else if (limit === true) {
limit = this.constructor.maximum.pixels; limit = Math.pow(0x3FFF, 2);
} }
if (is.integer(limit) && limit >= 0) { if (is.integer(limit) && limit >= 0) {
this.options.limitInputPixels = limit; this.options.limitInputPixels = limit;
} else { } else {
throw new Error('Invalid pixel limit (0 to ' + this.constructor.maximum.pixels + ') ' + limit); throw is.invalidParameterError('limitInputPixels', 'integer', limit);
} }
return this; return this;
}; }
/** /**
* An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. * An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
@@ -225,10 +259,10 @@ const limitInputPixels = function limitInputPixels (limit) {
* @param {Boolean} [sequentialRead=true] * @param {Boolean} [sequentialRead=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const sequentialRead = function sequentialRead (sequentialRead) { function sequentialRead (sequentialRead) {
this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true; this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with input-related functions. * Decorate the Sharp prototype with input-related functions.

View File

@@ -16,6 +16,14 @@ const object = function (val) {
return typeof val === 'object'; return typeof val === 'object';
}; };
/**
* Is this value a plain object?
* @private
*/
const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]';
};
/** /**
* Is this value a function? * Is this value a function?
* @private * @private
@@ -98,6 +106,7 @@ const invalidParameterError = function (name, expected, actual) {
module.exports = { module.exports = {
defined: defined, defined: defined,
object: object, object: object,
plainObject: plainObject,
fn: fn, fn: fn,
bool: bool, bool: bool,
buffer: buffer, buffer: buffer,

View File

@@ -6,7 +6,10 @@ const is = require('./is');
* Rotate the output image by either an explicit angle * Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag. * or auto-orient based on the EXIF `Orientation` tag.
* *
* Use this method without angle to determine the angle from EXIF data. * If an angle is provided, it is converted to a valid 90/180/270deg rotation.
* For example, `-450` will produce a 270deg rotation.
*
* If no angle is provided, it is determined from the EXIF data.
* Mirroring is supported and may infer the use of a flip operation. * Mirroring is supported and may infer the use of a flip operation.
* *
* The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -25,20 +28,20 @@ const is = require('./is');
* }); * });
* readableStream.pipe(pipeline); * readableStream.pipe(pipeline);
* *
* @param {Number} [angle=auto] 0, 90, 180 or 270. * @param {Number} [angle=auto] angle of rotation, must be a multiple of 90.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const rotate = function rotate (angle) { function rotate (angle) {
if (!is.defined(angle)) { if (!is.defined(angle)) {
this.options.angle = -1; this.options.useExifOrientation = true;
} else if (is.integer(angle) && is.inArray(angle, [0, 90, 180, 270])) { } else if (is.integer(angle) && !(angle % 90)) {
this.options.angle = angle; this.options.angle = angle;
} else { } else {
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle); throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + angle);
} }
return this; return this;
}; }
/** /**
* Extract a region of the image. * Extract a region of the image.
@@ -70,7 +73,7 @@ const rotate = function rotate (angle) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const extract = function extract (options) { function extract (options) {
const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
['left', 'top', 'width', 'height'].forEach(function (name) { ['left', 'top', 'width', 'height'].forEach(function (name) {
const value = options[name]; const value = options[name];
@@ -81,11 +84,11 @@ const extract = function extract (options) {
} }
}, this); }, this);
// Ensure existing rotation occurs before pre-resize extraction // Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && this.options.angle !== 0) { if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true)) {
this.options.rotateBeforePreExtract = true; this.options.rotateBeforePreExtract = true;
} }
return this; return this;
}; }
/** /**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any. * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
@@ -93,10 +96,10 @@ const extract = function extract (options) {
* @param {Boolean} [flip=true] * @param {Boolean} [flip=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flip = function flip (flip) { function flip (flip) {
this.options.flip = is.bool(flip) ? flip : true; this.options.flip = is.bool(flip) ? flip : true;
return this; return this;
}; }
/** /**
* Flop the image about the horizontal X axis. This always occurs after rotation, if any. * Flop the image about the horizontal X axis. This always occurs after rotation, if any.
@@ -104,10 +107,10 @@ const flip = function flip (flip) {
* @param {Boolean} [flop=true] * @param {Boolean} [flop=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flop = function flop (flop) { function flop (flop) {
this.options.flop = is.bool(flop) ? flop : true; this.options.flop = is.bool(flop) ? flop : true;
return this; return this;
}; }
/** /**
* Sharpen the image. * Sharpen the image.
@@ -121,7 +124,7 @@ const flop = function flop (flop) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const sharpen = function sharpen (sigma, flat, jagged) { function sharpen (sigma, flat, jagged) {
if (!is.defined(sigma)) { if (!is.defined(sigma)) {
// No arguments: default to mild sharpen // No arguments: default to mild sharpen
this.options.sharpenSigma = -1; this.options.sharpenSigma = -1;
@@ -151,7 +154,7 @@ const sharpen = function sharpen (sigma, flat, jagged) {
throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma); throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
} }
return this; return this;
}; }
/** /**
* Blur the image. * Blur the image.
@@ -161,7 +164,7 @@ const sharpen = function sharpen (sigma, flat, jagged) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const blur = function blur (sigma) { function blur (sigma) {
if (!is.defined(sigma)) { if (!is.defined(sigma)) {
// No arguments: default to mild blur // No arguments: default to mild blur
this.options.blurSigma = -1; this.options.blurSigma = -1;
@@ -175,7 +178,7 @@ const blur = function blur (sigma) {
throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma); throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma);
} }
return this; return this;
}; }
/** /**
* Extends/pads the edges of the image with the colour provided to the `background` method. * Extends/pads the edges of the image with the colour provided to the `background` method.
@@ -198,7 +201,7 @@ const blur = function blur (sigma) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const extend = function extend (extend) { function extend (extend) {
if (is.integer(extend) && extend > 0) { if (is.integer(extend) && extend > 0) {
this.options.extendTop = extend; this.options.extendTop = extend;
this.options.extendBottom = extend; this.options.extendBottom = extend;
@@ -219,17 +222,17 @@ const extend = function extend (extend) {
throw new Error('Invalid edge extension ' + extend); throw new Error('Invalid edge extension ' + extend);
} }
return this; return this;
}; }
/** /**
* Merge alpha transparency channel, if any, with `background`. * Merge alpha transparency channel, if any, with `background`.
* @param {Boolean} [flatten=true] * @param {Boolean} [flatten=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const flatten = function flatten (flatten) { function flatten (flatten) {
this.options.flatten = is.bool(flatten) ? flatten : true; this.options.flatten = is.bool(flatten) ? flatten : true;
return this; return this;
}; }
/** /**
* Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. * Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel.
@@ -237,7 +240,7 @@ const flatten = function flatten (flatten) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const trim = function trim (tolerance) { function trim (tolerance) {
if (!is.defined(tolerance)) { if (!is.defined(tolerance)) {
this.options.trimTolerance = 10; this.options.trimTolerance = 10;
} else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) { } else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) {
@@ -246,7 +249,7 @@ const trim = function trim (tolerance) {
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance); throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
} }
return this; return this;
}; }
/** /**
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
@@ -258,7 +261,7 @@ const trim = function trim (tolerance) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const gamma = function gamma (gamma) { function gamma (gamma) {
if (!is.defined(gamma)) { if (!is.defined(gamma)) {
// Default gamma correction of 2.2 (sRGB) // Default gamma correction of 2.2 (sRGB)
this.options.gamma = 2.2; this.options.gamma = 2.2;
@@ -268,36 +271,36 @@ const gamma = function gamma (gamma) {
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma); throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
} }
return this; return this;
}; }
/** /**
* Produce the "negative" of the image. * Produce the "negative" of the image.
* @param {Boolean} [negate=true] * @param {Boolean} [negate=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const negate = function negate (negate) { function negate (negate) {
this.options.negate = is.bool(negate) ? negate : true; this.options.negate = is.bool(negate) ? negate : true;
return this; return this;
}; }
/** /**
* Enhance output image contrast by stretching its luminance to cover the full dynamic range. * Enhance output image contrast by stretching its luminance to cover the full dynamic range.
* @param {Boolean} [normalise=true] * @param {Boolean} [normalise=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const normalise = function normalise (normalise) { function normalise (normalise) {
this.options.normalise = is.bool(normalise) ? normalise : true; this.options.normalise = is.bool(normalise) ? normalise : true;
return this; return this;
}; }
/** /**
* Alternative spelling of normalise. * Alternative spelling of normalise.
* @param {Boolean} [normalize=true] * @param {Boolean} [normalize=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const normalize = function normalize (normalize) { function normalize (normalize) {
return this.normalise(normalize); return this.normalise(normalize);
}; }
/** /**
* Convolve the image with the specified kernel. * Convolve the image with the specified kernel.
@@ -324,7 +327,7 @@ const normalize = function normalize (normalize) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const convolve = function convolve (kernel) { function convolve (kernel) {
if (!is.object(kernel) || !Array.isArray(kernel.kernel) || if (!is.object(kernel) || !Array.isArray(kernel.kernel) ||
!is.integer(kernel.width) || !is.integer(kernel.height) || !is.integer(kernel.width) || !is.integer(kernel.height) ||
!is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) || !is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) ||
@@ -348,7 +351,7 @@ const convolve = function convolve (kernel) {
} }
this.options.convKernel = kernel; this.options.convKernel = kernel;
return this; return this;
}; }
/** /**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
@@ -359,7 +362,7 @@ const convolve = function convolve (kernel) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const threshold = function threshold (threshold, options) { function threshold (threshold, options) {
if (!is.defined(threshold)) { if (!is.defined(threshold)) {
this.options.threshold = 128; this.options.threshold = 128;
} else if (is.bool(threshold)) { } else if (is.bool(threshold)) {
@@ -375,7 +378,7 @@ const threshold = function threshold (threshold, options) {
this.options.thresholdGrayscale = false; this.options.thresholdGrayscale = false;
} }
return this; return this;
}; }
/** /**
* Perform a bitwise boolean operation with operand image. * Perform a bitwise boolean operation with operand image.
@@ -393,7 +396,7 @@ const threshold = function threshold (threshold, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const boolean = function boolean (operand, operator, options) { function boolean (operand, operator, options) {
this.options.boolean = this._createInputDescriptor(operand, options); this.options.boolean = this._createInputDescriptor(operand, options);
if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) { if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) {
this.options.booleanOp = operator; this.options.booleanOp = operator;
@@ -401,7 +404,7 @@ const boolean = function boolean (operand, operator, options) {
throw new Error('Invalid boolean operator ' + operator); throw new Error('Invalid boolean operator ' + operator);
} }
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with operation-related functions. * Decorate the Sharp prototype with operation-related functions.

View File

@@ -1,6 +1,5 @@
'use strict'; 'use strict';
const util = require('util');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
@@ -15,11 +14,12 @@ const sharp = require('../build/Release/sharp.node');
* *
* @param {String} fileOut - the path to write the image data to. * @param {String} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`. * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
* @returns {Promise<Object>} - when no callback is provided * @returns {Promise<Object>} - when no callback is provided
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const toFile = function toFile (fileOut, callback) { function toFile (fileOut, callback) {
if (!fileOut || fileOut.length === 0) { if (!fileOut || fileOut.length === 0) {
const errOutputInvalid = new Error('Invalid output'); const errOutputInvalid = new Error('Invalid output');
if (is.fn(callback)) { if (is.fn(callback)) {
@@ -41,25 +41,33 @@ const toFile = function toFile (fileOut, callback) {
} }
} }
return this; return this;
}; }
/** /**
* Write output to a Buffer. * Write output to a Buffer.
* JPEG, PNG, WebP, and RAW output are supported. * JPEG, PNG, WebP, TIFF and RAW output are supported.
* By default, the format will match the input image, except GIF and SVG input which become PNG output. * By default, the format will match the input image, except GIF and SVG input which become PNG output.
* *
* `callback`, if present, gets three arguments `(err, buffer, info)` where: * `callback`, if present, gets three arguments `(err, data, info)` where:
* - `err` is an error message, if any. * - `err` is an error, if any.
* - `buffer` is the output image data. * - `data` is the output image data.
* - `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* A Promises/A+ promise is returned when `callback` is not provided. * `channels` and `premultiplied` (indicating if premultiplication was used).
* A Promise is returned when `callback` is not provided.
* *
* @param {Object} [options]
* @param {Boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback] * @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided * @returns {Promise<Buffer>} - when no callback is provided
*/ */
const toBuffer = function toBuffer (callback) { function toBuffer (options, callback) {
return this._pipeline(callback); if (is.object(options)) {
}; if (is.bool(options.resolveWithObject)) {
this.options.resolveWithObject = options.resolveWithObject;
}
}
return this._pipeline(is.fn(options) ? options : callback);
}
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
@@ -70,7 +78,7 @@ const toBuffer = function toBuffer (callback) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const withMetadata = function withMetadata (withMetadata) { function withMetadata (withMetadata) {
this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true; this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true;
if (is.object(withMetadata)) { if (is.object(withMetadata)) {
if (is.defined(withMetadata.orientation)) { if (is.defined(withMetadata.orientation)) {
@@ -82,7 +90,7 @@ const withMetadata = function withMetadata (withMetadata) {
} }
} }
return this; return this;
}; }
/** /**
* Use these JPEG options for output image. * Use these JPEG options for output image.
@@ -98,7 +106,7 @@ const withMetadata = function withMetadata (withMetadata) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const jpeg = function jpeg (options) { function jpeg (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.quality)) { if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
@@ -133,7 +141,7 @@ const jpeg = function jpeg (options) {
} }
} }
return this._updateFormatOut('jpeg', options); return this._updateFormatOut('jpeg', options);
}; }
/** /**
* Use these PNG options for output image. * Use these PNG options for output image.
@@ -145,7 +153,7 @@ const jpeg = function jpeg (options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const png = function png (options) { function png (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.progressive)) { if (is.defined(options.progressive)) {
this._setBooleanOption('pngProgressive', options.progressive); this._setBooleanOption('pngProgressive', options.progressive);
@@ -162,17 +170,20 @@ const png = function png (options) {
} }
} }
return this._updateFormatOut('png', options); return this._updateFormatOut('png', options);
}; }
/** /**
* Use these WebP options for output image. * Use these WebP options for output image.
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {Boolean} [options.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const webp = function webp (options) { function webp (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options) && is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality; this.options.webpQuality = options.quality;
@@ -180,18 +191,36 @@ const webp = function webp (options) {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality); throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
} }
} }
if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 1, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw new Error('Invalid webp alpha quality (integer, 1-100) ' + options.alphaQuality);
}
}
if (is.object(options) && is.defined(options.lossless)) {
this._setBooleanOption('webpLossless', options.lossless);
}
if (is.object(options) && is.defined(options.nearLossless)) {
this._setBooleanOption('webpNearLossless', options.nearLossless);
}
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
}; }
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg
* @param {Boolean} [options.predictor='none'] - compression predictor options: none, horizontal, float
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
const tiff = function tiff (options) { function tiff (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options) && is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.tiffQuality = options.quality; this.options.tiffQuality = options.quality;
@@ -199,16 +228,56 @@ const tiff = function tiff (options) {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality); throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
} }
} }
if (is.object(options) && is.defined(options.squash)) {
if (is.bool(options.squash)) {
this.options.tiffSquash = options.squash;
} else {
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
}
}
// resolution
if (is.object(options) && is.defined(options.xres)) {
if (is.number(options.xres)) {
this.options.tiffXres = options.xres;
} else {
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
}
}
if (is.object(options) && is.defined(options.yres)) {
if (is.number(options.yres)) {
this.options.tiffYres = options.yres;
} else {
throw new Error('Invalid Value for yres ' + options.yres + ' Only numeric values allowed for options.yres');
}
}
// compression
if (is.defined(options) && is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) {
this.options.tiffCompression = options.compression;
} else {
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, none`;
throw new Error(message);
}
}
// predictor
if (is.defined(options) && is.defined(options.predictor)) {
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
this.options.tiffPredictor = options.predictor;
} else {
const message = `Invalid predictor option "${options.predictor}". Should be one of: none, horizontal, float`;
throw new Error(message);
}
}
return this._updateFormatOut('tiff', options); return this._updateFormatOut('tiff', options);
}; }
/** /**
* Force output to be raw, uncompressed uint8 pixel data. * Force output to be raw, uncompressed uint8 pixel data.
* @returns {Sharp} * @returns {Sharp}
*/ */
const raw = function raw () { function raw () {
return this._updateFormatOut('raw'); return this._updateFormatOut('raw');
}; }
/** /**
* Force output to a given format. * Force output to a given format.
@@ -217,15 +286,16 @@ const raw = function raw () {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} unsupported format or options * @throws {Error} unsupported format or options
*/ */
const toFormat = function toFormat (format, options) { function toFormat (format, options) {
if (is.object(format) && is.string(format.id)) { if (is.object(format) && is.string(format.id)) {
format = format.id; format = format.id;
} }
if (format === 'jpg') format = 'jpeg';
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) { if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
throw new Error('Unsupported output format ' + format); throw new Error('Unsupported output format ' + format);
} }
return this[format](options); return this[format](options);
}; }
/** /**
* Use tile-based deep zoom (image pyramid) output. * Use tile-based deep zoom (image pyramid) output.
@@ -251,7 +321,7 @@ const toFormat = function toFormat (format, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const tile = function tile (tile) { function tile (tile) {
if (is.object(tile)) { if (is.object(tile)) {
// Size of square tiles, in pixels // Size of square tiles, in pixels
if (is.defined(tile.size)) { if (is.defined(tile.size)) {
@@ -296,7 +366,7 @@ const tile = function tile (tile) {
throw new Error('Invalid tile format ' + this.options.formatOut); throw new Error('Invalid tile format ' + this.options.formatOut);
} }
return this._updateFormatOut('dz'); return this._updateFormatOut('dz');
}; }
/** /**
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
@@ -307,10 +377,10 @@ const tile = function tile (tile) {
* @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format * @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
*/ */
const _updateFormatOut = function _updateFormatOut (formatOut, options) { function _updateFormatOut (formatOut, options) {
this.options.formatOut = (is.object(options) && options.force === false) ? 'input' : formatOut; this.options.formatOut = (is.object(options) && options.force === false) ? 'input' : formatOut;
return this; return this;
}; }
/** /**
* Update a Boolean attribute of the this.options Object. * Update a Boolean attribute of the this.options Object.
@@ -319,31 +389,31 @@ const _updateFormatOut = function _updateFormatOut (formatOut, options) {
* @param {Boolean} val * @param {Boolean} val
* @throws {Error} Invalid key * @throws {Error} Invalid key
*/ */
const _setBooleanOption = function _setBooleanOption (key, val) { function _setBooleanOption (key, val) {
if (is.bool(val)) { if (is.bool(val)) {
this.options[key] = val; this.options[key] = val;
} else { } else {
throw new Error('Invalid ' + key + ' (boolean) ' + val); throw new Error('Invalid ' + key + ' (boolean) ' + val);
} }
}; }
/** /**
* Called by a WriteableStream to notify us it is ready for data. * Called by a WriteableStream to notify us it is ready for data.
* @private * @private
*/ */
const _read = function _read () { function _read () {
if (!this.options.streamOut) { if (!this.options.streamOut) {
this.options.streamOut = true; this.options.streamOut = true;
this._pipeline(); this._pipeline();
} }
}; }
/** /**
* Invoke the C++ image processing pipeline * Invoke the C++ image processing pipeline
* Supports callback, stream and promise variants * Supports callback, stream and promise variants
* @private * @private
*/ */
const _pipeline = function _pipeline (callback) { function _pipeline (callback) {
const that = this; const that = this;
if (typeof callback === 'function') { if (typeof callback === 'function') {
// output=file/buffer // output=file/buffer
@@ -362,9 +432,9 @@ const _pipeline = function _pipeline (callback) {
// output=stream // output=stream
if (this._isStreamInput()) { if (this._isStreamInput()) {
// output=stream, input=stream // output=stream, input=stream
this.on('finish', function () { if (this.streamInFinished) {
that._flattenBufferIn(); this._flattenBufferIn();
sharp.pipeline(that.options, function (err, data, info) { sharp.pipeline(this.options, function (err, data, info) {
if (err) { if (err) {
that.emit('error', err); that.emit('error', err);
} else { } else {
@@ -373,7 +443,20 @@ const _pipeline = function _pipeline (callback) {
} }
that.push(null); that.push(null);
}); });
}); } else {
this.on('finish', function () {
that._flattenBufferIn();
sharp.pipeline(that.options, function (err, data, info) {
if (err) {
that.emit('error', err);
} else {
that.emit('info', info);
that.push(data);
}
that.push(null);
});
});
}
} else { } else {
// output=stream, input=file/buffer // output=stream, input=file/buffer
sharp.pipeline(this.options, function (err, data, info) { sharp.pipeline(this.options, function (err, data, info) {
@@ -394,11 +477,15 @@ const _pipeline = function _pipeline (callback) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
that.on('finish', function () { that.on('finish', function () {
that._flattenBufferIn(); that._flattenBufferIn();
sharp.pipeline(that.options, function (err, data) { sharp.pipeline(that.options, function (err, data, info) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); if (that.options.resolveWithObject) {
resolve({ data: data, info: info });
} else {
resolve(data);
}
} }
}); });
}); });
@@ -406,77 +493,21 @@ const _pipeline = function _pipeline (callback) {
} else { } else {
// output=promise, input=file/buffer // output=promise, input=file/buffer
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
sharp.pipeline(that.options, function (err, data) { sharp.pipeline(that.options, function (err, data, info) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); if (that.options.resolveWithObject) {
resolve({ data: data, info: info });
} else {
resolve(data);
}
} }
}); });
}); });
} }
} }
}; }
// Deprecated output options
/* istanbul ignore next */
const quality = util.deprecate(function (quality) {
const formatOut = this.options.formatOut;
const options = { quality: quality };
this.jpeg(options).webp(options).tiff(options);
this.options.formatOut = formatOut;
return this;
}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead');
/* istanbul ignore next */
const progressive = util.deprecate(function (progressive) {
const formatOut = this.options.formatOut;
const options = { progressive: (progressive !== false) };
this.jpeg(options).png(options);
this.options.formatOut = formatOut;
return this;
}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead');
/* istanbul ignore next */
const compressionLevel = util.deprecate(function (compressionLevel) {
const formatOut = this.options.formatOut;
this.png({ compressionLevel: compressionLevel });
this.options.formatOut = formatOut;
return this;
}, 'compressionLevel: use png({ compressionLevel: ... }) instead');
/* istanbul ignore next */
const withoutAdaptiveFiltering = util.deprecate(function (withoutAdaptiveFiltering) {
const formatOut = this.options.formatOut;
this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) });
this.options.formatOut = formatOut;
return this;
}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead');
/* istanbul ignore next */
const withoutChromaSubsampling = util.deprecate(function (withoutChromaSubsampling) {
const formatOut = this.options.formatOut;
this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' });
this.options.formatOut = formatOut;
return this;
}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead');
/* istanbul ignore next */
const trellisQuantisation = util.deprecate(function (trellisQuantisation) {
const formatOut = this.options.formatOut;
this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) });
this.options.formatOut = formatOut;
return this;
}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead');
/* istanbul ignore next */
const overshootDeringing = util.deprecate(function (overshootDeringing) {
const formatOut = this.options.formatOut;
this.jpeg({ overshootDeringing: (overshootDeringing !== false) });
this.options.formatOut = formatOut;
return this;
}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead');
/* istanbul ignore next */
const optimiseScans = util.deprecate(function (optimiseScans) {
const formatOut = this.options.formatOut;
this.jpeg({ optimiseScans: (optimiseScans !== false) });
this.options.formatOut = formatOut;
return this;
}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead');
/** /**
* Decorate the Sharp prototype with output-related functions. * Decorate the Sharp prototype with output-related functions.
@@ -503,15 +534,4 @@ module.exports = function (Sharp) {
].forEach(function (f) { ].forEach(function (f) {
Sharp.prototype[f.name] = f; Sharp.prototype[f.name] = f;
}); });
// Deprecated
Sharp.prototype.quality = quality;
Sharp.prototype.progressive = progressive;
Sharp.prototype.compressionLevel = compressionLevel;
Sharp.prototype.withoutAdaptiveFiltering = withoutAdaptiveFiltering;
Sharp.prototype.withoutChromaSubsampling = withoutChromaSubsampling;
Sharp.prototype.trellisQuantisation = trellisQuantisation;
Sharp.prototype.trellisQuantization = trellisQuantisation;
Sharp.prototype.overshootDeringing = overshootDeringing;
Sharp.prototype.optimiseScans = optimiseScans;
Sharp.prototype.optimizeScans = optimiseScans;
}; };

View File

@@ -36,6 +36,7 @@ const strategy = {
* @private * @private
*/ */
const kernel = { const kernel = {
nearest: 'nearest',
cubic: 'cubic', cubic: 'cubic',
lanczos2: 'lanczos2', lanczos2: 'lanczos2',
lanczos3: 'lanczos3' lanczos3: 'lanczos3'
@@ -62,6 +63,7 @@ const interpolator = {
* By default, the resized image is centre cropped to the exact size specified. * By default, the resized image is centre cropped to the exact size specified.
* *
* Possible reduction kernels are: * Possible reduction kernels are:
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
@@ -89,8 +91,8 @@ const interpolator = {
* // of the image data in inputBuffer * // of the image data in inputBuffer
* }); * });
* *
* @param {Number} [width] - pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. * @param {Number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {Number} [height] - pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. * @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options] * @param {Object} [options]
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction. * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
* @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement. * @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement.
@@ -99,21 +101,21 @@ const interpolator = {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const resize = function resize (width, height, options) { function resize (width, height, options) {
if (is.defined(width)) { if (is.defined(width)) {
if (is.integer(width) && is.inRange(width, 1, this.constructor.maximum.width)) { if (is.integer(width) && width > 0) {
this.options.width = width; this.options.width = width;
} else { } else {
throw is.invalidParameterError('width', `integer between 1 and ${this.constructor.maximum.width}`, width); throw is.invalidParameterError('width', 'positive integer', width);
} }
} else { } else {
this.options.width = -1; this.options.width = -1;
} }
if (is.defined(height)) { if (is.defined(height)) {
if (is.integer(height) && is.inRange(height, 1, this.constructor.maximum.height)) { if (is.integer(height) && height > 0) {
this.options.height = height; this.options.height = height;
} else { } else {
throw is.invalidParameterError('height', `integer between 1 and ${this.constructor.maximum.height}`, height); throw is.invalidParameterError('height', 'positive integer', height);
} }
} else { } else {
this.options.height = -1; this.options.height = -1;
@@ -142,7 +144,7 @@ const resize = function resize (width, height, options) {
} }
} }
return this; return this;
}; }
/** /**
* Crop the resized image to the exact size specified, the default behaviour. * Crop the resized image to the exact size specified, the default behaviour.
@@ -170,7 +172,7 @@ const resize = function resize (width, height, options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const crop = function crop (crop) { function crop (crop) {
this.options.canvas = 'crop'; this.options.canvas = 'crop';
if (!is.defined(crop)) { if (!is.defined(crop)) {
// Default // Default
@@ -184,11 +186,14 @@ const crop = function crop (crop) {
} else if (is.integer(crop) && crop >= strategy.entropy) { } else if (is.integer(crop) && crop >= strategy.entropy) {
// Strategy // Strategy
this.options.crop = crop; this.options.crop = crop;
} else if (is.string(crop) && is.integer(strategy[crop])) {
// Strategy (string)
this.options.crop = strategy[crop];
} else { } else {
throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop); throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop);
} }
return this; return this;
}; }
/** /**
* Preserving aspect ratio, resize the image to the maximum `width` or `height` specified * Preserving aspect ratio, resize the image to the maximum `width` or `height` specified
@@ -213,10 +218,10 @@ const crop = function crop (crop) {
* *
* @returns {Sharp} * @returns {Sharp}
*/ */
const embed = function embed () { function embed () {
this.options.canvas = 'embed'; this.options.canvas = 'embed';
return this; return this;
}; }
/** /**
* Preserving aspect ratio, resize the image to be as large as possible * Preserving aspect ratio, resize the image to be as large as possible
@@ -237,10 +242,10 @@ const embed = function embed () {
* *
* @returns {Sharp} * @returns {Sharp}
*/ */
const max = function max () { function max () {
this.options.canvas = 'max'; this.options.canvas = 'max';
return this; return this;
}; }
/** /**
* Preserving aspect ratio, resize the image to be as small as possible * Preserving aspect ratio, resize the image to be as small as possible
@@ -250,20 +255,20 @@ const max = function max () {
* *
* @returns {Sharp} * @returns {Sharp}
*/ */
const min = function min () { function min () {
this.options.canvas = 'min'; this.options.canvas = 'min';
return this; return this;
}; }
/** /**
* Ignoring the aspect ratio of the input, stretch the image to * Ignoring the aspect ratio of the input, stretch the image to
* the exact `width` and/or `height` provided via `resize`. * the exact `width` and/or `height` provided via `resize`.
* @returns {Sharp} * @returns {Sharp}
*/ */
const ignoreAspectRatio = function ignoreAspectRatio () { function ignoreAspectRatio () {
this.options.canvas = 'ignore_aspect'; this.options.canvas = 'ignore_aspect';
return this; return this;
}; }
/** /**
* Do not enlarge the output image if the input image width *or* height are already less than the required dimensions. * Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
@@ -272,10 +277,10 @@ const ignoreAspectRatio = function ignoreAspectRatio () {
* @param {Boolean} [withoutEnlargement=true] * @param {Boolean} [withoutEnlargement=true]
* @returns {Sharp} * @returns {Sharp}
*/ */
const withoutEnlargement = function withoutEnlargement (withoutEnlargement) { function withoutEnlargement (withoutEnlargement) {
this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true; this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true;
return this; return this;
}; }
/** /**
* Decorate the Sharp prototype with resize-related functions. * Decorate the Sharp prototype with resize-related functions.

View File

@@ -4,7 +4,7 @@ const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
/** /**
* Gets, or when options are provided sets, the limits of _libvips'_ operation cache. * Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
* Existing entries in the cache will be trimmed after any change in limits. * Existing entries in the cache will be trimmed after any change in limits.
* This method always returns cache statistics, * This method always returns cache statistics,
* useful for determining how much working memory is required for a particular task. * useful for determining how much working memory is required for a particular task.
@@ -16,13 +16,13 @@ const sharp = require('../build/Release/sharp.node');
* sharp.cache( { files: 0 } ); * sharp.cache( { files: 0 } );
* sharp.cache(false); * sharp.cache(false);
* *
* @param {Object|Boolean} options - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching. * @param {Object|Boolean} [options=true] - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching
* @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache * @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {Number} [options.files=20] - is the maximum number of files to hold open * @param {Number} [options.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache * @param {Number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object} * @returns {Object}
*/ */
const cache = function cache (options) { function cache (options) {
if (is.bool(options)) { if (is.bool(options)) {
if (options) { if (options) {
// Default cache settings of 50MB, 20 files, 100 items // Default cache settings of 50MB, 20 files, 100 items
@@ -35,11 +35,11 @@ const cache = function cache (options) {
} else { } else {
return sharp.cache(); return sharp.cache();
} }
}; }
cache(true); cache(true);
/** /**
* Gets, or when a concurrency is provided sets, * Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image. * the number of threads _libvips'_ should create to process each image.
* The default value is the number of CPU cores. * The default value is the number of CPU cores.
* A value of `0` will reset to this default. * A value of `0` will reset to this default.
@@ -57,9 +57,9 @@ cache(true);
* @param {Number} [concurrency] * @param {Number} [concurrency]
* @returns {Number} concurrency * @returns {Number} concurrency
*/ */
const concurrency = function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}; }
/** /**
* Provides access to internal task counters. * Provides access to internal task counters.
@@ -71,9 +71,9 @@ const concurrency = function concurrency (concurrency) {
* *
* @returns {Object} * @returns {Object}
*/ */
const counters = function counters () { function counters () {
return sharp.counters(); return sharp.counters();
}; }
/** /**
* Get and set use of SIMD vector unit instructions. * Get and set use of SIMD vector unit instructions.
@@ -95,9 +95,9 @@ const counters = function counters () {
* @param {Boolean} [simd=false] * @param {Boolean} [simd=false]
* @returns {Boolean} * @returns {Boolean}
*/ */
const simd = function simd (simd) { function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null); return sharp.simd(is.bool(simd) ? simd : null);
}; }
simd(false); simd(false);
/** /**

View File

@@ -1,14 +1,13 @@
site_name: sharp site_name: sharp
site_url: http://sharp.dimens.io/ site_url: http://sharp.pixelplumbing.com/
repo_url: https://github.com/lovell/sharp repo_url: https://github.com/lovell/sharp
site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
copyright: <a href="https://dimens.io/">dimens.io</a> copyright: <a href="https://pixelplumbing.com/">pixelplumbing.com</a>
google_analytics: ['UA-13034748-12', 'sharp.dimens.io'] google_analytics: ['UA-13034748-12', 'sharp.pixelplumbing.com']
theme: readthedocs theme: readthedocs
markdown_extensions: markdown_extensions:
- toc: - toc:
permalink: True permalink: True
dev_addr: 0.0.0.0:10101
pages: pages:
- Home: index.md - Home: index.md
- Installation: install.md - Installation: install.md

View File

@@ -1,8 +1,9 @@
{ {
"name": "sharp", "name": "sharp",
"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",
"version": "0.17.1", "version": "0.18.3",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",
"Jonathan Ong <jonathanrichardong@gmail.com>", "Jonathan Ong <jonathanrichardong@gmail.com>",
@@ -30,11 +31,18 @@
"Matt Hirsch <mhirsch@media.mit.edu>", "Matt Hirsch <mhirsch@media.mit.edu>",
"Matthias Thoemmes <thoemmes@gmail.com>", "Matthias Thoemmes <thoemmes@gmail.com>",
"Patrick Paskaris <patrick@paskaris.gr>", "Patrick Paskaris <patrick@paskaris.gr>",
"Jérémy Lal <kapouer@melix.org>" "Jérémy Lal <kapouer@melix.org>",
"Rahul Nanwani <r.nanwani@gmail.com>",
"Alice Monday <alice0meta@gmail.com>",
"Kristo Jorgenson <kristo.jorgenson@gmail.com>",
"YvesBos <yves_bos@outlook.com>",
"Guy Maliar <guy@tailorbrands.com>",
"Nicolas Coden <nicolas@ncoden.fr>",
"Matt Parrish <matt.r.parrish@gmail.com>"
], ],
"scripts": { "scripts": {
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*",
"test": "semistandard && cross-env VIPS_WARNING=0 nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "test": "semistandard && cc && nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test-linux-x64.sh", "test-packaging": "./packaging/test-linux-x64.sh",
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md lib/$m.js >docs/api-$m.md; done" "docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md lib/$m.js >docs/api-$m.md; done"
@@ -61,36 +69,43 @@
], ],
"dependencies": { "dependencies": {
"caw": "^2.0.0", "caw": "^2.0.0",
"color": "^1.0.3", "color": "^2.0.0",
"got": "^6.7.1", "detect-libc": "^0.2.0",
"nan": "^2.5.0", "nan": "^2.6.2",
"semver": "^5.3.0", "semver": "^5.3.0",
"tar": "^2.2.1" "simple-get": "^2.7.0",
"tar": "^3.1.5"
}, },
"devDependencies": { "devDependencies": {
"async": "^2.1.4", "async": "^2.5.0",
"bufferutil": "^1.3.0", "cc": "^1.0.1",
"cross-env": "^3.1.4", "documentation": "^4.0.0-rc.1",
"documentation": "^4.0.0-beta.18",
"exif-reader": "^1.0.2", "exif-reader": "^1.0.2",
"icc": "^1.0.0", "icc": "^1.0.0",
"mocha": "^3.2.0", "mocha": "^3.4.2",
"node-cpplint": "^0.4.0", "nyc": "^11.0.3",
"nyc": "^10.0.0", "rimraf": "^2.6.1",
"rimraf": "^2.5.4", "semistandard": "^11.0.0",
"semistandard": "^9.2.1",
"unzip": "^0.1.11" "unzip": "^0.1.11"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.4.2" "libvips": "8.5.5"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=4.5.0"
}, },
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"
] ]
},
"cc": {
"linelength": "120",
"filter": [
"build/c++11",
"build/include",
"runtime/indentation_namespace"
]
} }
} }

View File

@@ -23,6 +23,11 @@ if ! type docker >/dev/null; then
exit 1 exit 1
fi fi
# Update base images
for baseimage in debian:wheezy debian:jessie debian:stretch socialdefect/raspbian-jessie-core; do
docker pull $baseimage
done
# Windows (x64) # Windows (x64)
if [ $PLATFORM = "all" ] || [ $PLATFORM = "win32-x64" ]; then if [ $PLATFORM = "all" ] || [ $PLATFORM = "win32-x64" ]; then
echo "Building win32-x64..." echo "Building win32-x64..."

View File

@@ -16,27 +16,28 @@ export CFLAGS="${FLAGS}"
export CXXFLAGS="${FLAGS}" export CXXFLAGS="${FLAGS}"
# Dependency version numbers # Dependency version numbers
VERSION_ZLIB=1.2.10 VERSION_ZLIB=1.2.11
VERSION_FFI=3.2.1 VERSION_FFI=3.2.1
VERSION_GLIB=2.50.1 VERSION_GLIB=2.53.1
VERSION_XML2=2.9.4 VERSION_XML2=2.9.4
VERSION_GSF=1.14.40 VERSION_GSF=1.14.41
VERSION_EXIF=0.6.21 VERSION_EXIF=0.6.21
VERSION_LCMS2=2.8 VERSION_LCMS2=2.8
VERSION_JPEG=1.5.1 VERSION_JPEG=1.5.1
VERSION_PNG16=1.6.28 VERSION_PNG16=1.6.29
VERSION_WEBP=0.5.1 VERSION_WEBP=0.6.0
VERSION_TIFF=4.0.6 VERSION_TIFF=4.0.7
VERSION_ORC=0.4.26 VERSION_ORC=0.4.26
VERSION_GDKPIXBUF=2.36.0 VERSION_GDKPIXBUF=2.36.6
VERSION_FREETYPE=2.7 VERSION_FREETYPE=2.8
VERSION_EXPAT=2.2.0
VERSION_FONTCONFIG=2.12.1 VERSION_FONTCONFIG=2.12.1
VERSION_HARFBUZZ=1.3.2 VERSION_HARFBUZZ=1.4.6
VERSION_PIXMAN=0.34.0 VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6 VERSION_CAIRO=1.14.8
VERSION_PANGO=1.40.3 VERSION_PANGO=1.40.5
VERSION_CROCO=0.6.11 VERSION_CROCO=0.6.12
VERSION_SVG=2.40.16 VERSION_SVG=2.40.17
VERSION_GIF=5.1.4 VERSION_GIF=5.1.4
# Least out-of-sync Sourceforge mirror # Least out-of-sync Sourceforge mirror
@@ -56,11 +57,12 @@ cd ${DEPS}/ffi
make install-strip make install-strip
mkdir ${DEPS}/glib mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.50/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1 curl -Ls https://download.gnome.org/sources/glib/2.53/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib cd ${DEPS}/glib
echo glib_cv_stack_grows=no >>glib.cache echo glib_cv_stack_grows=no >>glib.cache
echo glib_cv_uscore=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 --disable-libmount ./configure --cache-file=glib.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--with-pcre=internal --disable-libmount
make install-strip make install-strip
mkdir ${DEPS}/xml2 mkdir ${DEPS}/xml2
@@ -87,6 +89,9 @@ 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
# Apply patches for lcms2 vulnerabilities reported since v2.8
VERSION_LCMS2_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/mm2/Little-CMS/git/refs/heads/master | jq -r '.object.sha' | head -c7)
curl -Ls https://github.com/mm2/Little-CMS/compare/lcms2.8...master.patch | patch -p1 -t || true
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip make install-strip
@@ -106,15 +111,16 @@ 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 --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-neon ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-neon --enable-libwebpmux
make install-strip 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
# Apply patches for various libtiff security vulnerabilities reported since v4.0.6 # Apply patches for libtiff vulnerabilities reported since v4.0.7
VERSION_TIFF_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/vadz/libtiff/git/refs/heads/master | jq -r '.object.sha' | head -c7) VERSION_TIFF_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/vadz/libtiff/git/refs/heads/master | jq -r '.object.sha' | head -c7)
curl -Ls https://github.com/vadz/libtiff/compare/Release-v4-0-6...master.patch | patch -p1 -t || true curl -Ls https://github.com/vadz/libtiff/compare/Release-v4-0-7...master.patch | patch -p1 -t || true
if [ -n "${CHOST}" ]; then autoreconf -fiv; fi if [ -n "${CHOST}" ]; then autoreconf -fiv; fi
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-mdi --disable-pixarlog --disable-cxx ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-mdi --disable-pixarlog --disable-cxx
make install-strip make install-strip
@@ -130,8 +136,11 @@ rm -rf liborc-test-*
mkdir ${DEPS}/gdkpixbuf mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.36/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1 curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.36/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf cd ${DEPS}/gdkpixbuf
touch gdk-pixbuf/loaders.cache
LD_LIBRARY_PATH=${TARGET}/lib \ LD_LIBRARY_PATH=${TARGET}/lib \
./configure --cache-file=gdkpixbuf.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-modules --disable-gio-sniffing --without-libtiff --without-gdiplus --with-included-loaders=png,jpeg ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --disable-gio-sniffing \
--without-libtiff --without-gdiplus --with-included-loaders=png,jpeg
make install-strip make install-strip
mkdir ${DEPS}/freetype mkdir ${DEPS}/freetype
@@ -140,10 +149,17 @@ cd ${DEPS}/freetype
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static
make install make install
mkdir ${DEPS}/expat
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/expat/expat/${VERSION_EXPAT}/expat-${VERSION_EXPAT}.tar.bz2 | tar xjC ${DEPS}/expat --strip-components=1
cd ${DEPS}/expat
./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 --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 --sysconfdir=/etc ./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--with-expat-includes=${TARGET}/include --with-expat-lib=${TARGET}/lib --sysconfdir=/etc
make install-strip make install-strip
mkdir ${DEPS}/harfbuzz mkdir ${DEPS}/harfbuzz
@@ -191,7 +207,7 @@ cd ${DEPS}/gif
make install-strip make install-strip
mkdir ${DEPS}/vips mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.4/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1 curl -Ls https://github.com/jcupitt/libvips/releases/download/v${VERSION_VIPS}/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips cd ${DEPS}/vips
./configure --host=${CHOST} --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 \
@@ -212,6 +228,7 @@ echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\ \"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\ \"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\ \"exif\": \"${VERSION_EXIF}\",\n\
\"expat\": \"${VERSION_EXPAT}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\ \"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\ \"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\ \"freetype\": \"${VERSION_FREETYPE}\",\n\
@@ -221,7 +238,7 @@ echo "{\n\
\"gsf\": \"${VERSION_GSF}\",\n\ \"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\ \"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\ \"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\ \"lcms\": \"${VERSION_LCMS2}-${VERSION_LCMS2_GIT_MASTER_SHA}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\ \"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\ \"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\ \"pixman\": \"${VERSION_PIXMAN}\",\n\

View File

@@ -8,14 +8,11 @@ curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VI
unzip vips-dev-w64-web-${VERSION_VIPS}.zip unzip vips-dev-w64-web-${VERSION_VIPS}.zip
# Clean and zip # Clean and zip
cd /vips/vips-dev-8.4 cd /vips/vips-dev-8.5
rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
cp bin/*.dll lib/ cp bin/*.dll lib/
cp -r lib64/* lib/ cp -r lib64/* lib/
# Temp patch for __declspec ordering
curl -L -o include/vips/VImage8.h https://raw.githubusercontent.com/lovell/libvips/e1aef0445bf123d2de000bc7f2ef97b9f788eea0/cplusplus/include/vips/VImage8.h
echo "Creating tarball" 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 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" echo "Shrinking tarball"

View File

@@ -9,10 +9,10 @@ RUN \
apt-get install -y curl && \ apt-get install -y curl && \
dpkg --add-architecture arm64 && \ dpkg --add-architecture arm64 && \
apt-get update && \ apt-get update && \
apt-get install -y crossbuild-essential-arm64 autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq apt-get install -y crossbuild-essential-arm64 autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq gettext intltool autopoint
# Compiler settings # Compiler settings
ENV \ ENV \
PLATFORM=linux-armv8 \ PLATFORM=linux-armv8 \
CHOST=aarch64-linux-gnu \ CHOST=aarch64-linux-gnu \
FLAGS="-march=armv8-a -Os" FLAGS="-march=armv8-a -Os -D_GLIBCXX_USE_CXX11_ABI=0"

View File

@@ -6,15 +6,14 @@ if ! type docker >/dev/null; then
exit 1 exit 1
fi fi
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"
# Debian 7, 8 # Debian 7, 8
# Ubuntu 14.04 # Ubuntu 14.04, 16.04
for dist in wheezy jessie trusty; do for dist in debian:jessie debian:stretch ubuntu:trusty ubuntu:xenial; do
echo "Testing $dist..." echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test"; docker pull $dist
if docker run -i -t --rm -v $PWD:/v $dist >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
then echo "$dist OK" then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log else echo "$dist fail" && cat packaging/$dist.log
fi fi
@@ -22,35 +21,16 @@ 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"; docker pull centos:7
if docker run -i -t --rm -v $PWD:/v centos:7 >packaging/centos7.log 2>&1 sh -c "cd /v; ./packaging/test/centos.sh; $test";
then echo "centos7 OK" then echo "centos7 OK"
else echo "centos7 fail" && cat packaging/$dist.log else echo "centos7 fail" && cat packaging/centos7.log
fi fi
# Fedora 22 # Archlinux latest
echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "fedora22 OK"
else echo "fedora22 fail" && cat packaging/$dist.log
fi
# openSUSE 13.2
echo "Testing opensuse..."
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
then echo "opensuse OK"
else echo "opensuse fail" && cat packaging/opensuse.log
fi
# Archlinux 2015.06.01
echo "Testing archlinux..." echo "Testing archlinux..."
docker pull pritunl/archlinux:latest
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"; 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
# Alpine
echo "Testing alpine..."
if docker run -i -t --rm -v $PWD:/v -e "SHARP_TEST_WITHOUT_CACHE=0" alpine:edge >packaging/alpine.log 2>&1 sh -c "cd /v; ./packaging/test/alpine.sh; $test";
then echo "alpine OK"
else echo "alpine fail" && cat packaging/alpine.log
fi

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Install build dependencies
apk add --update make gcc g++ python nodejs
# Install libvips from aports/testing
apk add --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing vips-dev

4
packaging/test/centos.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
curl -sL https://rpm.nodesource.com/setup_6.x | bash -
yum install -y gcc-c++ make nodejs

View File

@@ -1,5 +1,6 @@
#!/bin/sh #!/bin/sh
# Install pkg-config on Debian/Ubuntu
apt-get update apt-get update
apt-get install -y pkg-config apt-get install -y build-essential python pkg-config curl
curl -sL https://deb.nodesource.com/setup_6.x | bash -
apt-get install -y nodejs

View File

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

View File

@@ -1,4 +1,4 @@
FROM debian:jessie FROM debian:stretch
MAINTAINER Lovell Fuller <npm@lovell.info> MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for post-processing Windows x64 binaries # Create Debian-based container suitable for post-processing Windows x64 binaries

View File

@@ -1,12 +1,29 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdlib> #include <cstdlib>
#include <string> #include <string>
#include <string.h> #include <string.h>
#include <vector>
#include <queue>
#include <mutex>
#include <node.h> #include <node.h>
#include <node_buffer.h> #include <node_buffer.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h"
#include "common.h" #include "common.h"
using vips::VImage; using vips::VImage;
@@ -29,7 +46,7 @@ namespace sharp {
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = node::Buffer::Data(buffer);
@@ -45,6 +62,16 @@ namespace sharp {
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
} }
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
v8::Local<v8::Object> createBackground = AttrAs<v8::Object>(input, "createBackground");
for (unsigned int i = 0; i < 4; i++) {
descriptor->createBackground[i] = AttrTo<double>(createBackground, i);
}
}
return descriptor; return descriptor;
} }
@@ -177,7 +204,6 @@ namespace sharp {
VImage image; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->buffer != nullptr) { if (descriptor->buffer != nullptr) {
// From buffer
if (descriptor->rawChannels > 0) { if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength, image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@@ -212,26 +238,41 @@ namespace sharp {
} }
} }
} else { } else {
// From filesystem if (descriptor->createChannels > 0) {
imageType = DetermineImageType(descriptor->file.data()); // Create new image
if (imageType != ImageType::UNKNOWN) { std::vector<double> background = {
try { descriptor->createBackground[0],
vips::VOption *option = VImage::option()->set("access", accessMethod); descriptor->createBackground[1],
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { descriptor->createBackground[2]
option->set("dpi", static_cast<double>(descriptor->density)); };
} if (descriptor->createChannels == 4) {
if (imageType == ImageType::MAGICK) { background.push_back(descriptor->createBackground[3]);
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");
} }
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
imageType = ImageType::RAW;
} else { } else {
throw vips::VError("Input file is missing or of an unsupported image format"); // 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); return std::make_tuple(image, imageType);
@@ -254,8 +295,7 @@ namespace sharp {
return ( return (
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) || (bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) || (bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK) (bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
);
} }
/* /*
@@ -307,6 +347,25 @@ namespace sharp {
image.set(VIPS_META_RESOLUTION_UNIT, "in"); image.set(VIPS_META_RESOLUTION_UNIT, "in");
} }
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) {
throw vips::VError("Processed image is too large for the JPEG format");
}
} else if (imageType == ImageType::PNG) {
if (image.width() > 2147483647 || image.height() > 2147483647) {
throw vips::VError("Processed image is too large for the PNG format");
}
} else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) {
throw vips::VError("Processed image is too large for the WebP format");
}
}
}
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
@@ -316,6 +375,33 @@ namespace sharp {
} }
} }
/*
Temporary buffer of warnings
*/
std::queue<std::string> vipsWarnings;
std::mutex vipsWarningsMutex;
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
vipsWarnings.emplace(message);
}
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop() {
std::string warning;
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
if (!vipsWarnings.empty()) {
warning = vipsWarnings.front();
vipsWarnings.pop();
}
return warning;
}
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity. within the input image, applying the given gravity.
@@ -352,9 +438,11 @@ namespace sharp {
// Southeast // Southeast
left = inWidth - outWidth; left = inWidth - outWidth;
top = inHeight - outHeight; top = inHeight - outHeight;
break;
case 7: case 7:
// Southwest // Southwest
top = inHeight - outHeight; top = inHeight - outHeight;
break;
case 8: case 8:
// Northwest // Northwest
break; break;
@@ -378,23 +466,23 @@ namespace sharp {
int top = 0; int top = 0;
// assign only if valid // assign only if valid
if(x >= 0 && x < (inWidth - outWidth)) { if (x >= 0 && x < (inWidth - outWidth)) {
left = x; left = x;
} else if(x >= (inWidth - outWidth)) { } else if (x >= (inWidth - outWidth)) {
left = inWidth - outWidth; left = inWidth - outWidth;
} }
if(y >= 0 && y < (inHeight - outHeight)) { if (y >= 0 && y < (inHeight - outHeight)) {
top = y; top = y;
} else if(y >= (inHeight - outHeight)) { } else if (y >= (inHeight - outHeight)) {
top = inHeight - outHeight; top = inHeight - outHeight;
} }
// the resulting left and top could have been outside the image after calculation from bottom/right edges // the resulting left and top could have been outside the image after calculation from bottom/right edges
if(left < 0) { if (left < 0) {
left = 0; left = 0;
} }
if(top < 0) { if (top < 0) {
top = 0; top = 0;
} }
@@ -421,8 +509,7 @@ namespace sharp {
*/ */
VipsOperationBoolean GetBooleanOperation(std::string const opStr) { VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
return static_cast<VipsOperationBoolean>( return static_cast<VipsOperationBoolean>(
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()) vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()));
);
} }
/* /*
@@ -430,8 +517,7 @@ namespace sharp {
*/ */
VipsInterpretation GetInterpretation(std::string const typeStr) { VipsInterpretation GetInterpretation(std::string const typeStr) {
return static_cast<VipsInterpretation>( return static_cast<VipsInterpretation>(
vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()) vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()));
);
} }
/* /*

View File

@@ -1,18 +1,32 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_COMMON_H_ #ifndef SRC_COMMON_H_
#define SRC_COMMON_H_ #define SRC_COMMON_H_
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <vector>
#include <node.h> #include <node.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h"
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 4)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 5))
#error libvips version 8.4.x required - see sharp.dimens.io/page/install #error libvips version 8.5.x required - see sharp.dimens.io/page/install
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -38,6 +52,10 @@ namespace sharp {
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
int createChannels;
int createWidth;
int createHeight;
double createBackground[4];
InputDescriptor(): InputDescriptor():
buffer(nullptr), buffer(nullptr),
@@ -45,7 +63,15 @@ namespace sharp {
density(72), density(72),
rawChannels(0), rawChannels(0),
rawWidth(0), rawWidth(0),
rawHeight(0) {} rawHeight(0),
createChannels(0),
createWidth(0),
createHeight(0) {
createBackground[0] = 0.0;
createBackground[1] = 0.0;
createBackground[2] = 0.0;
createBackground[3] = 255.0;
}
}; };
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a v8::Object
@@ -63,8 +89,7 @@ namespace sharp {
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> buffersToPersist);
);
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -159,11 +184,26 @@ namespace sharp {
*/ */
void SetDensity(VImage image, const int density); void SetDensity(VImage image, const int density);
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType);
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint); void FreeCallback(char* data, void* hint);
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore);
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop();
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity. within the input image, applying the given gravity.

View File

@@ -350,7 +350,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( vips_object_get_argument( object, name, if( vips_object_get_argument( object, name,
&pspec, &argument_class, &argument_instance ) ) { &pspec, &argument_class, &argument_instance ) ) {
vips_warn( NULL, "%s", vips_error_buffer() ); g_warning( "%s", vips_error_buffer() );
vips_error_clear(); vips_error_clear();
return; return;
} }
@@ -364,7 +364,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( (enum_value = vips_enum_from_nick( object_class->nickname, if( (enum_value = vips_enum_from_nick( object_class->nickname,
pspec_type, g_value_get_string( value ) )) < 0 ) { pspec_type, g_value_get_string( value ) )) < 0 ) {
vips_warn( NULL, "%s", vips_error_buffer() ); g_warning( "%s", vips_error_buffer() );
vips_error_clear(); vips_error_clear();
return; return;
} }
@@ -770,19 +770,19 @@ operator+( VImage a, std::vector<double> b )
} }
VImage & VImage &
operator+=( VImage a, const VImage b ) operator+=( VImage &a, const VImage b )
{ {
return( a = a + b ); return( a = a + b );
} }
VImage & VImage &
operator+=( VImage a, const double b ) operator+=( VImage &a, const double b )
{ {
return( a = a + b ); return( a = a + b );
} }
VImage & VImage &
operator+=( VImage a, std::vector<double> b ) operator+=( VImage &a, std::vector<double> b )
{ {
return( a = a + b ); return( a = a + b );
} }
@@ -818,19 +818,19 @@ operator-( VImage a, std::vector<double> b )
} }
VImage & VImage &
operator-=( VImage a, const VImage b ) operator-=( VImage &a, const VImage b )
{ {
return( a = a - b ); return( a = a - b );
} }
VImage & VImage &
operator-=( VImage a, const double b ) operator-=( VImage &a, const double b )
{ {
return( a = a - b ); return( a = a - b );
} }
VImage & VImage &
operator-=( VImage a, std::vector<double> b ) operator-=( VImage &a, std::vector<double> b )
{ {
return( a = a - b ); return( a = a - b );
} }
@@ -872,19 +872,19 @@ operator*( VImage a, std::vector<double> b )
} }
VImage & VImage &
operator*=( VImage a, const VImage b ) operator*=( VImage &a, const VImage b )
{ {
return( a = a * b ); return( a = a * b );
} }
VImage & VImage &
operator*=( VImage a, const double b ) operator*=( VImage &a, const double b )
{ {
return( a = a * b ); return( a = a * b );
} }
VImage & VImage &
operator*=( VImage a, std::vector<double> b ) operator*=( VImage &a, std::vector<double> b )
{ {
return( a = a * b ); return( a = a * b );
} }
@@ -920,19 +920,19 @@ operator/( VImage a, std::vector<double> b )
} }
VImage & VImage &
operator/=( VImage a, const VImage b ) operator/=( VImage &a, const VImage b )
{ {
return( a = a / b ); return( a = a / b );
} }
VImage & VImage &
operator/=( VImage a, const double b ) operator/=( VImage &a, const double b )
{ {
return( a = a / b ); return( a = a / b );
} }
VImage & VImage &
operator/=( VImage a, std::vector<double> b ) operator/=( VImage &a, std::vector<double> b )
{ {
return( a = a / b ); return( a = a / b );
} }
@@ -956,19 +956,19 @@ operator%( VImage a, std::vector<double> b )
} }
VImage & VImage &
operator%=( VImage a, const VImage b ) operator%=( VImage &a, const VImage b )
{ {
return( a = a % b ); return( a = a % b );
} }
VImage & VImage &
operator%=( VImage a, const double b ) operator%=( VImage &a, const double b )
{ {
return( a = a % b ); return( a = a % b );
} }
VImage & VImage &
operator%=( VImage a, std::vector<double> b ) operator%=( VImage &a, std::vector<double> b )
{ {
return( a = a % b ); return( a = a % b );
} }
@@ -982,29 +982,29 @@ operator<( VImage a, VImage b )
VImage VImage
operator<( double a, VImage b ) operator<( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
VIPS_OPERATION_RELATIONAL_MORE ) ); to_vector( a ) ) );
} }
VImage VImage
operator<( VImage a, double b ) operator<( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
VIPS_OPERATION_RELATIONAL_LESS ) ); to_vector( b ) ) );
} }
VImage VImage
operator<( std::vector<double> a, VImage b ) operator<( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
VIPS_OPERATION_RELATIONAL_MORE ) ); a ) );
} }
VImage VImage
operator<( VImage a, std::vector<double> b ) operator<( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
VIPS_OPERATION_RELATIONAL_LESS ) ); b ) );
} }
VImage VImage
@@ -1016,29 +1016,29 @@ operator<=( VImage a, VImage b )
VImage VImage
operator<=( double a, VImage b ) operator<=( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
VIPS_OPERATION_RELATIONAL_MOREEQ ) ); to_vector( a ) ) );
} }
VImage VImage
operator<=( VImage a, double b ) operator<=( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
VIPS_OPERATION_RELATIONAL_LESSEQ ) ); to_vector( b ) ) );
} }
VImage VImage
operator<=( std::vector<double> a, VImage b ) operator<=( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
VIPS_OPERATION_RELATIONAL_MOREEQ ) ); a ) );
} }
VImage VImage
operator<=( VImage a, std::vector<double> b ) operator<=( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
VIPS_OPERATION_RELATIONAL_LESSEQ ) ); b ) );
} }
VImage VImage
@@ -1050,29 +1050,29 @@ operator>( VImage a, VImage b )
VImage VImage
operator>( double a, VImage b ) operator>( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
VIPS_OPERATION_RELATIONAL_LESS ) ); to_vector( a ) ) );
} }
VImage VImage
operator>( VImage a, double b ) operator>( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
VIPS_OPERATION_RELATIONAL_MORE ) ); to_vector( b ) ) );
} }
VImage VImage
operator>( std::vector<double> a, VImage b ) operator>( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
VIPS_OPERATION_RELATIONAL_LESS ) ); a ) );
} }
VImage VImage
operator>( VImage a, std::vector<double> b ) operator>( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
VIPS_OPERATION_RELATIONAL_MORE ) ); b ) );
} }
VImage VImage
@@ -1084,29 +1084,29 @@ operator>=( VImage a, VImage b )
VImage VImage
operator>=( double a, VImage b ) operator>=( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
VIPS_OPERATION_RELATIONAL_LESSEQ ) ); to_vector( a ) ) );
} }
VImage VImage
operator>=( VImage a, double b ) operator>=( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
VIPS_OPERATION_RELATIONAL_MOREEQ ) ); to_vector( b ) ) );
} }
VImage VImage
operator>=( std::vector<double> a, VImage b ) operator>=( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
VIPS_OPERATION_RELATIONAL_LESSEQ ) ); a ) );
} }
VImage VImage
operator>=( VImage a, std::vector<double> b ) operator>=( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
VIPS_OPERATION_RELATIONAL_MOREEQ ) ); b ) );
} }
VImage VImage
@@ -1118,29 +1118,29 @@ operator==( VImage a, VImage b )
VImage VImage
operator==( double a, VImage b ) operator==( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
VIPS_OPERATION_RELATIONAL_EQUAL ) ); to_vector( a ) ) );
} }
VImage VImage
operator==( VImage a, double b ) operator==( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
VIPS_OPERATION_RELATIONAL_EQUAL ) ); to_vector( b ) ) );
} }
VImage VImage
operator==( std::vector<double> a, VImage b ) operator==( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
VIPS_OPERATION_RELATIONAL_EQUAL ) ); a ) );
} }
VImage VImage
operator==( VImage a, std::vector<double> b ) operator==( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
VIPS_OPERATION_RELATIONAL_EQUAL ) ); b ) );
} }
VImage VImage
@@ -1152,29 +1152,29 @@ operator!=( VImage a, VImage b )
VImage VImage
operator!=( double a, VImage b ) operator!=( double a, VImage b )
{ {
return( b.relational_const( to_vector( a ), return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
VIPS_OPERATION_RELATIONAL_NOTEQ ) ); to_vector( a ) ) );
} }
VImage VImage
operator!=( VImage a, double b ) operator!=( VImage a, double b )
{ {
return( a.relational_const( to_vector( b ), return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
VIPS_OPERATION_RELATIONAL_NOTEQ ) ); to_vector( b ) ) );
} }
VImage VImage
operator!=( std::vector<double> a, VImage b ) operator!=( std::vector<double> a, VImage b )
{ {
return( b.relational_const( a, return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
VIPS_OPERATION_RELATIONAL_NOTEQ ) ); a ) );
} }
VImage VImage
operator!=( VImage a, std::vector<double> b ) operator!=( VImage a, std::vector<double> b )
{ {
return( a.relational_const( b, return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
VIPS_OPERATION_RELATIONAL_NOTEQ ) ); b ) );
} }
VImage VImage
@@ -1186,43 +1186,43 @@ operator&( VImage a, VImage b )
VImage VImage
operator&( double a, VImage b ) operator&( double a, VImage b )
{ {
return( b.boolean_const( to_vector( a ), return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND,
VIPS_OPERATION_BOOLEAN_AND ) ); to_vector( a ) ) );
} }
VImage VImage
operator&( VImage a, double b ) operator&( VImage a, double b )
{ {
return( a.boolean_const( to_vector( b ), return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND,
VIPS_OPERATION_BOOLEAN_AND ) ); to_vector( b ) ) );
} }
VImage VImage
operator&( std::vector<double> a, VImage b ) operator&( std::vector<double> a, VImage b )
{ {
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_AND ) ); return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND, a ) );
} }
VImage VImage
operator&( VImage a, std::vector<double> b ) operator&( VImage a, std::vector<double> b )
{ {
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_AND ) ); return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND, b ) );
} }
VImage & VImage &
operator&=( VImage a, const VImage b ) operator&=( VImage &a, const VImage b )
{ {
return( a = a & b ); return( a = a & b );
} }
VImage & VImage &
operator&=( VImage a, const double b ) operator&=( VImage &a, const double b )
{ {
return( a = a & b ); return( a = a & b );
} }
VImage & VImage &
operator&=( VImage a, std::vector<double> b ) operator&=( VImage &a, std::vector<double> b )
{ {
return( a = a & b ); return( a = a & b );
} }
@@ -1236,43 +1236,45 @@ operator|( VImage a, VImage b )
VImage VImage
operator|( double a, VImage b ) operator|( double a, VImage b )
{ {
return( b.boolean_const( to_vector( a ), return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
VIPS_OPERATION_BOOLEAN_OR ) ); to_vector( a ) ) );
} }
VImage VImage
operator|( VImage a, double b ) operator|( VImage a, double b )
{ {
return( a.boolean_const( to_vector( b ), return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
VIPS_OPERATION_BOOLEAN_OR ) ); to_vector( b ) ) );
} }
VImage VImage
operator|( std::vector<double> a, VImage b ) operator|( std::vector<double> a, VImage b )
{ {
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_OR ) ); return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
a ) );
} }
VImage VImage
operator|( VImage a, std::vector<double> b ) operator|( VImage a, std::vector<double> b )
{ {
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_OR ) ); return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
b ) );
} }
VImage & VImage &
operator|=( VImage a, const VImage b ) operator|=( VImage &a, const VImage b )
{ {
return( a = a | b ); return( a = a | b );
} }
VImage & VImage &
operator|=( VImage a, const double b ) operator|=( VImage &a, const double b )
{ {
return( a = a | b ); return( a = a | b );
} }
VImage & VImage &
operator|=( VImage a, std::vector<double> b ) operator|=( VImage &a, std::vector<double> b )
{ {
return( a = a | b ); return( a = a | b );
} }
@@ -1286,43 +1288,45 @@ operator^( VImage a, VImage b )
VImage VImage
operator^( double a, VImage b ) operator^( double a, VImage b )
{ {
return( b.boolean_const( to_vector( a ), return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
VIPS_OPERATION_BOOLEAN_EOR ) ); to_vector( a ) ) );
} }
VImage VImage
operator^( VImage a, double b ) operator^( VImage a, double b )
{ {
return( a.boolean_const( to_vector( b ), return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
VIPS_OPERATION_BOOLEAN_EOR ) ); to_vector( b ) ) );
} }
VImage VImage
operator^( std::vector<double> a, VImage b ) operator^( std::vector<double> a, VImage b )
{ {
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_EOR ) ); return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
a ) );
} }
VImage VImage
operator^( VImage a, std::vector<double> b ) operator^( VImage a, std::vector<double> b )
{ {
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_EOR ) ); return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
b ) );
} }
VImage & VImage &
operator^=( VImage a, const VImage b ) operator^=( VImage &a, const VImage b )
{ {
return( a = a ^ b ); return( a = a ^ b );
} }
VImage & VImage &
operator^=( VImage a, const double b ) operator^=( VImage &a, const double b )
{ {
return( a = a ^ b ); return( a = a ^ b );
} }
VImage & VImage &
operator^=( VImage a, std::vector<double> b ) operator^=( VImage &a, std::vector<double> b )
{ {
return( a = a ^ b ); return( a = a ^ b );
} }
@@ -1336,30 +1340,31 @@ operator<<( VImage a, VImage b )
VImage VImage
operator<<( VImage a, double b ) operator<<( VImage a, double b )
{ {
return( a.boolean_const( to_vector( b ), return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT,
VIPS_OPERATION_BOOLEAN_LSHIFT ) ); to_vector( b ) ) );
} }
VImage VImage
operator<<( VImage a, std::vector<double> b ) operator<<( VImage a, std::vector<double> b )
{ {
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) ); return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT,
b ) );
} }
VImage & VImage &
operator<<=( VImage a, const VImage b ) operator<<=( VImage &a, const VImage b )
{ {
return( a = a << b ); return( a = a << b );
} }
VImage & VImage &
operator<<=( VImage a, const double b ) operator<<=( VImage &a, const double b )
{ {
return( a = a << b ); return( a = a << b );
} }
VImage & VImage &
operator<<=( VImage a, std::vector<double> b ) operator<<=( VImage &a, std::vector<double> b )
{ {
return( a = a << b ); return( a = a << b );
} }
@@ -1373,30 +1378,31 @@ operator>>( VImage a, VImage b )
VImage VImage
operator>>( VImage a, double b ) operator>>( VImage a, double b )
{ {
return( a.boolean_const( to_vector( b ), return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT,
VIPS_OPERATION_BOOLEAN_RSHIFT ) ); to_vector( b ) ) );
} }
VImage VImage
operator>>( VImage a, std::vector<double> b ) operator>>( VImage a, std::vector<double> b )
{ {
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) ); return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT,
b ) );
} }
VImage & VImage &
operator>>=( VImage a, const VImage b ) operator>>=( VImage &a, const VImage b )
{ {
return( a = a << b ); return( a = a << b );
} }
VImage & VImage &
operator>>=( VImage a, const double b ) operator>>=( VImage &a, const double b )
{ {
return( a = a << b ); return( a = a << b );
} }
VImage & VImage &
operator>>=( VImage a, std::vector<double> b ) operator>>=( VImage &a, std::vector<double> b )
{ {
return( a = a << b ); return( a = a << b );
} }

View File

@@ -1,5 +1,5 @@
// bodies for vips operations // bodies for vips operations
// Thu 18 Aug 16:01:57 BST 2016 // Mon 13 Mar 13:22:17 GMT 2017
// this file is generated automatically, do not edit! // this file is generated automatically, do not edit!
void VImage::system( char * cmd_format , VOption *options ) void VImage::system( char * cmd_format , VOption *options )
@@ -231,7 +231,7 @@ VImage VImage::round( VipsOperationRound round , VOption *options )
return( out ); return( out );
} }
VImage VImage::relational_const( std::vector<double> c , VipsOperationRelational relational , VOption *options ) VImage VImage::relational_const( VipsOperationRelational relational , std::vector<double> c , VOption *options )
{ {
VImage out; VImage out;
@@ -239,8 +239,8 @@ VImage VImage::relational_const( std::vector<double> c , VipsOperationRelational
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) -> set( "out", &out ) ->
set( "c", c ) -> set( "relational", relational ) ->
set( "relational", relational ) ); set( "c", c ) );
return( out ); return( out );
} }
@@ -258,7 +258,7 @@ VImage VImage::remainder_const( std::vector<double> c , VOption *options )
return( out ); return( out );
} }
VImage VImage::boolean_const( std::vector<double> c , VipsOperationBoolean boolean , VOption *options ) VImage VImage::boolean_const( VipsOperationBoolean boolean , std::vector<double> c , VOption *options )
{ {
VImage out; VImage out;
@@ -266,13 +266,13 @@ VImage VImage::boolean_const( std::vector<double> c , VipsOperationBoolean boole
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) -> set( "out", &out ) ->
set( "c", c ) -> set( "boolean", boolean ) ->
set( "boolean", boolean ) ); set( "c", c ) );
return( out ); return( out );
} }
VImage VImage::math2_const( std::vector<double> c , VipsOperationMath2 math2 , VOption *options ) VImage VImage::math2_const( VipsOperationMath2 math2 , std::vector<double> c , VOption *options )
{ {
VImage out; VImage out;
@@ -280,8 +280,8 @@ VImage VImage::math2_const( std::vector<double> c , VipsOperationMath2 math2 , V
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) -> set( "out", &out ) ->
set( "c", c ) -> set( "math2", math2 ) ->
set( "math2", math2 ) ); set( "c", c ) );
return( out ); return( out );
} }
@@ -493,8 +493,8 @@ VImage VImage::copy( VOption *options )
call( "copy" , call( "copy" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -505,8 +505,8 @@ VImage VImage::tilecache( VOption *options )
call( "tilecache" , call( "tilecache" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -517,8 +517,8 @@ VImage VImage::linecache( VOption *options )
call( "linecache" , call( "linecache" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -529,8 +529,8 @@ VImage VImage::sequential( VOption *options )
call( "sequential" , call( "sequential" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -541,8 +541,8 @@ VImage VImage::cache( VOption *options )
call( "cache" , call( "cache" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -569,8 +569,8 @@ VImage VImage::flip( VipsDirection direction , VOption *options )
call( "flip" , call( "flip" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) ->
set( "direction", direction ) ); set( "direction", direction ) );
return( out ); return( out );
@@ -633,6 +633,20 @@ VImage VImage::extract_area( int left , int top , int width , int height , VOpti
return( out ); return( out );
} }
VImage VImage::smartcrop( int width , int height , VOption *options )
{
VImage out;
call( "smartcrop" ,
(options ? options : VImage::option()) ->
set( "input", *this ) ->
set( "out", &out ) ->
set( "width", width ) ->
set( "height", height ) );
return( out );
}
VImage VImage::extract_band( int band , VOption *options ) VImage VImage::extract_band( int band , VOption *options )
{ {
VImage out; VImage out;
@@ -728,8 +742,8 @@ VImage VImage::cast( VipsBandFormat format , VOption *options )
call( "cast" , call( "cast" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) ->
set( "format", format ) ); set( "format", format ) );
return( out ); return( out );
@@ -741,8 +755,8 @@ VImage VImage::rot( VipsAngle angle , VOption *options )
call( "rot" , call( "rot" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) ->
set( "angle", angle ) ); set( "angle", angle ) );
return( out ); return( out );
@@ -754,8 +768,8 @@ VImage VImage::rot45( VOption *options )
call( "rot45" , call( "rot45" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -766,8 +780,8 @@ VImage VImage::autorot( VOption *options )
call( "autorot" , call( "autorot" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -805,8 +819,8 @@ VImage VImage::bandfold( VOption *options )
call( "bandfold" , call( "bandfold" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -817,8 +831,8 @@ VImage VImage::bandunfold( VOption *options )
call( "bandunfold" , call( "bandunfold" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -829,8 +843,8 @@ VImage VImage::flatten( VOption *options )
call( "flatten" , call( "flatten" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -841,8 +855,8 @@ VImage VImage::premultiply( VOption *options )
call( "premultiply" , call( "premultiply" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -853,8 +867,8 @@ VImage VImage::unpremultiply( VOption *options )
call( "unpremultiply" , call( "unpremultiply" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -865,8 +879,8 @@ VImage VImage::grid( int tile_height , int across , int down , VOption *options
call( "grid" , call( "grid" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) -> set( "in", *this ) ->
set( "out", &out ) ->
set( "tile-height", tile_height ) -> set( "tile-height", tile_height ) ->
set( "across", across ) -> set( "across", across ) ->
set( "down", down ) ); set( "down", down ) );
@@ -880,8 +894,8 @@ VImage VImage::scale( VOption *options )
call( "scale" , call( "scale" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -892,8 +906,8 @@ VImage VImage::wrap( VOption *options )
call( "wrap" , call( "wrap" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -944,8 +958,8 @@ VImage VImage::byteswap( VOption *options )
call( "byteswap" , call( "byteswap" ,
(options ? options : VImage::option()) -> (options ? options : VImage::option()) ->
set( "out", &out ) -> set( "in", *this ) ->
set( "in", *this ) ); set( "out", &out ) );
return( out ); return( out );
} }
@@ -1757,6 +1771,18 @@ void VImage::dzsave( char * filename , VOption *options )
set( "filename", filename ) ); set( "filename", filename ) );
} }
VipsBlob * VImage::dzsave_buffer( VOption *options )
{
VipsBlob * buffer;
call( "dzsave_buffer" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::pngsave( char * filename , VOption *options ) void VImage::pngsave( char * filename , VOption *options )
{ {
call( "pngsave" , call( "pngsave" ,
@@ -1832,6 +1858,18 @@ void VImage::tiffsave( char * filename , VOption *options )
set( "filename", filename ) ); set( "filename", filename ) );
} }
VipsBlob * VImage::tiffsave_buffer( VOption *options )
{
VipsBlob * buffer;
call( "tiffsave_buffer" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::fitssave( char * filename , VOption *options ) void VImage::fitssave( char * filename , VOption *options )
{ {
call( "fitssave" , call( "fitssave" ,
@@ -1840,6 +1878,32 @@ void VImage::fitssave( char * filename , VOption *options )
set( "filename", filename ) ); set( "filename", filename ) );
} }
VImage VImage::thumbnail( char * filename , int width , VOption *options )
{
VImage out;
call( "thumbnail" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) ->
set( "width", width ) );
return( out );
}
VImage VImage::thumbnail_buffer( VipsBlob * buffer , int width , VOption *options )
{
VImage out;
call( "thumbnail_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) ->
set( "width", width ) );
return( out );
}
VImage VImage::mapim( VImage index , VOption *options ) VImage VImage::mapim( VImage index , VOption *options )
{ {
VImage out; VImage out;

View File

@@ -1,25 +1,40 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <numeric> #include <numeric>
#include <vector>
#include <node.h> #include <node.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h"
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Nan::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist std::vector<v8::Local<v8::Object>> const buffersToPersist) :
) : Nan::AsyncWorker(callback), baton(baton), buffersToPersist(buffersToPersist) { Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index // Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0, std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t { [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer); SaveToPersistent(index, buffer);
return index + 1; return index + 1;
} });
);
} }
~MetadataWorker() {} ~MetadataWorker() {}
@@ -42,6 +57,7 @@ class MetadataWorker : public Nan::AsyncWorker {
baton->height = image.height(); baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation()); baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands(); baton->channels = image.bands();
baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
if (sharp::HasDensity(image)) { if (sharp::HasDensity(image)) {
baton->density = sharp::GetDensity(image); baton->density = sharp::GetDensity(image);
} }
@@ -72,7 +88,7 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback () { void HandleOKCallback() {
using Nan::New; using Nan::New;
using Nan::Set; using Nan::Set;
Nan::HandleScope(); Nan::HandleScope();
@@ -88,6 +104,7 @@ class MetadataWorker : public Nan::AsyncWorker {
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height)); Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked()); Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels)); Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) { if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density)); Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
} }
@@ -99,14 +116,12 @@ class MetadataWorker : public Nan::AsyncWorker {
if (baton->exifLength > 0) { if (baton->exifLength > 0) {
Set(info, Set(info,
New("exif").ToLocalChecked(), New("exif").ToLocalChecked(),
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::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(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked() Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
);
} }
argv[1] = info; argv[1] = info;
} }
@@ -116,17 +131,25 @@ class MetadataWorker : public Nan::AsyncWorker {
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t { [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index); GetFromPersistent(index);
return index + 1; return index + 1;
} });
);
delete baton->input; delete baton->input;
delete baton; delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message);
warning = sharp::VipsWarningPop();
}
// Return to JavaScript // Return to JavaScript
callback->Call(2, argv); callback->Call(2, argv);
} }
private: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist; std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
@@ -144,9 +167,12 @@ NAN_METHOD(metadata) {
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist)); Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);

View File

@@ -1,8 +1,24 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_METADATA_H_ #ifndef SRC_METADATA_H_
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include "nan.h" #include <string>
#include "common.h" #include <nan.h>
#include "./common.h"
struct MetadataBaton { struct MetadataBaton {
// Input // Input
@@ -13,6 +29,7 @@ struct MetadataBaton {
int height; int height;
std::string space; std::string space;
int channels; int channels;
std::string depth;
int density; int density;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;

View File

@@ -1,7 +1,23 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <vector>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -13,69 +29,32 @@ using vips::VError;
namespace sharp { namespace sharp {
/* /*
Alpha composite src over dst with given gravity. Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after. Assumes alpha channels are already premultiplied and will be unpremultiplied after
*/ */
VImage Composite(VImage src, VImage dst, const int gravity) { VImage Composite(VImage image, VImage overlayImage, int const left, int const top) {
if(IsInputValidForComposition(src, dst)) { if (HasAlpha(overlayImage)) {
// Enlarge overlay src, if required // Alpha composite
if (src.width() < dst.width() || src.height() < dst.height()) { if (overlayImage.width() < image.width() || overlayImage.height() < image.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity. // Enlarge overlay
int left; std::vector<double> const background { 0.0, 0.0, 0.0, 0.0 };
int top; overlayImage = overlayImage.embed(left, top, image.width(), image.height(), VImage::option()
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND) ->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background) ->set("background", background));
);
} }
return CompositeImage(src, dst); return AlphaComposite(image, overlayImage);
} } else {
// If the input was not valid for composition the return the input image itself if (HasAlpha(image)) {
return dst; // Add alpha channel to overlayImage so channels match
} double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
overlayImage = overlayImage.bandjoin(
VImage Composite(VImage src, VImage dst, const int x, const int y) { VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
if(IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), x, y);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
} }
return CompositeImage(src, dst); return image.insert(overlayImage, left, top);
} }
// If the input was not valid for composition the return the input image itself
return dst;
} }
bool IsInputValidForComposition(VImage src, VImage dst) { VImage AlphaComposite(VImage dst, VImage src) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
if (!HasAlpha(src)) {
throw VError("Overlay image must have an alpha channel");
}
if (!HasAlpha(dst)) {
throw VError("Image to be overlaid must have an alpha channel");
}
if (src.width() > dst.width() || src.height() > dst.height()) {
throw VError("Overlay image must have same dimensions or smaller");
}
return true;
}
VImage CompositeImage(VImage src, VImage dst) {
// Split src into non-alpha and alpha channels // Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1)); VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0); VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
@@ -145,12 +124,11 @@ namespace sharp {
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 }; std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option() mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND) ->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background) ->set("background", background));
);
} }
// we use the mask alpha if it has alpha // we use the mask alpha if it has alpha
if(maskHasAlpha) { if (maskHasAlpha) {
mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));; mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));;
} }
@@ -284,123 +262,11 @@ namespace sharp {
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB; colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
} }
return image.sharpen( return image.sharpen(
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged) VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
).colourspace(colourspaceBeforeSharpen); .colourspace(colourspaceBeforeSharpen);
} }
} }
/*
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) {
// Flatten RGBA onto a mid-grey background
if (image.bands() == 4 && HasAlpha(image)) {
double const midgrey = sharp::Is16Bit(image.interpretation()) ? 32768.0 : 128.0;
std::vector<double> background { midgrey, midgrey, midgrey };
image = image.flatten(VImage::option()->set("background", background));
}
// 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
*/
std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy
) {
int left = 0;
int top = 0;
int const inWidth = image.width();
int const inHeight = image.height();
if (inWidth > outWidth) {
// Reduce width by repeated removing slices from edge with lowest score
int width = inWidth;
double leftScore = 0.0;
double rightScore = 0.0;
// Max width of each slice
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
while (width > outWidth) {
// Width of current slice
int const slice = std::min(width - outWidth, maxSliceWidth);
if (leftScore == 0.0) {
// Update score of left slice
leftScore = strategy(image.extract_area(left, 0, slice, inHeight));
}
if (rightScore == 0.0) {
// Update score of right slice
rightScore = strategy(image.extract_area(width - slice - 1, 0, slice, inHeight));
}
// Keep slice with highest score
if (leftScore >= rightScore) {
// Discard right slice
rightScore = 0.0;
} else {
// Discard left slice
leftScore = 0.0;
left = left + slice;
}
width = width - slice;
}
}
if (inHeight > outHeight) {
// Reduce height by repeated removing slices from edge with lowest score
int height = inHeight;
double topScore = 0.0;
double bottomScore = 0.0;
// Max height of each slice
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
while (height > outHeight) {
// Height of current slice
int const slice = std::min(height - outHeight, maxSliceHeight);
if (topScore == 0.0) {
// Update score of top slice
topScore = strategy(image.extract_area(0, top, inWidth, slice));
}
if (bottomScore == 0.0) {
// Update score of bottom slice
bottomScore = strategy(image.extract_area(0, height - slice - 1, inWidth, slice));
}
// Keep slice with highest score
if (topScore >= bottomScore) {
// Discard bottom slice
bottomScore = 0.0;
} else {
// Discard top slice
topScore = 0.0;
top = top + slice;
}
height = height - slice;
}
}
return std::make_tuple(left, top);
}
/* /*
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
*/ */
@@ -415,12 +281,11 @@ namespace sharp {
->set("tile_height", 10) ->set("tile_height", 10)
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0))) ->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
->set("access", VIPS_ACCESS_SEQUENTIAL) ->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("threaded", TRUE) ->set("threaded", TRUE));
);
} }
VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) { VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
if(!thresholdGrayscale) { if (!thresholdGrayscale) {
return image >= threshold; return image >= threshold;
} }
return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold; return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
@@ -485,7 +350,7 @@ namespace sharp {
int width = right - left; int width = right - left;
int height = bottom - top; int height = bottom - top;
if(width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance"); throw VError("Unexpected error while trimming. Try to lower the tolerance");
} }

View File

@@ -1,3 +1,17 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_OPERATIONS_H_ #ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_ #define SRC_OPERATIONS_H_
@@ -18,20 +32,14 @@ namespace sharp {
VImage Composite(VImage src, VImage dst, const int gravity); VImage Composite(VImage src, VImage dst, const int gravity);
/* /*
Alpha composite src over dst with given x and y offsets. Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/ */
VImage Composite(VImage src, VImage dst, const int x, const int y); VImage Composite(VImage image, VImage overlayImage, int const x, int const y);
/* /*
Check if the src and dst Images for composition operation are valid Alpha composite overlayImage over image, assumes matching dimensions
*/ */
bool IsInputValidForComposition(VImage src, VImage dst); VImage AlphaComposite(VImage image, VImage overlayImage);
/*
Given a valid src and dst, returns the composite of the two images
*/
VImage CompositeImage(VImage src, VImage dst);
/* /*
Cutout src over dst with given gravity. Cutout src over dst with given gravity.
@@ -64,23 +72,6 @@ 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);
/*
Crop strategy functors
*/
struct EntropyStrategy {
double operator()(VImage image);
};
struct AttentionStrategy {
double operator()(VImage image);
};
/*
Calculate crop area based on given strategy (Entropy, Attention)
*/
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
*/ */

View File

@@ -1,15 +1,31 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <tuple> #include <map>
#include <utility>
#include <memory> #include <memory>
#include <numeric> #include <numeric>
#include <map> #include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <vips/vips8> #include <vips/vips8>
#include <node.h> #include <node.h>
#include <nan.h>
#include "nan.h"
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
#include "pipeline.h" #include "pipeline.h"
@@ -17,16 +33,16 @@
class PipelineWorker : public Nan::AsyncWorker { class PipelineWorker : public Nan::AsyncWorker {
public: public:
PipelineWorker( PipelineWorker(
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *queueListener, Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener,
std::vector<v8::Local<v8::Object>> const buffersToPersist std::vector<v8::Local<v8::Object>> const buffersToPersist) :
) : Nan::AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) { Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog), queueListener(queueListener),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index // Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0, std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t { [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer); SaveToPersistent(index, buffer);
return index + 1; return index + 1;
} });
);
} }
~PipelineWorker() {} ~PipelineWorker() {}
@@ -65,16 +81,15 @@ class PipelineWorker : public Nan::AsyncWorker {
// Calculate angle of rotation // Calculate angle of rotation
VipsAngle rotation; VipsAngle rotation;
bool flip; if (baton->useExifOrientation) {
bool flop; // Rotate and flip image according to Exif orientation
std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image); bool flip;
if (flip && !baton->flip) { bool flop;
// Add flip operation due to EXIF mirroring std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
baton->flip = TRUE; baton->flip = baton->flip || flip;
} baton->flop = baton->flop || flop;
if (flop && !baton->flop) { } else {
// Add flip operation due to EXIF mirroring rotation = CalculateAngleRotation(baton->angle);
baton->flop = TRUE;
} }
// Rotate pre-extract // Rotate pre-extract
@@ -84,7 +99,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Trim // Trim
if(baton->trimTolerance != 0) { if (baton->trimTolerance != 0) {
image = sharp::Trim(image, baton->trimTolerance); image = sharp::Trim(image, baton->trimTolerance);
} }
@@ -205,12 +220,12 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// If integral x and y shrink are equal, try to use shrink-on-load for JPEG and WebP, // If integral x and y shrink are equal, try to use shrink-on-load for JPEG and WebP,
// but not when applying gamma correction or pre-resize extract // but not when applying gamma correction, pre-resize extract or trim
int shrink_on_load = 1; int shrink_on_load = 1;
if ( if (
xshrink == yshrink && xshrink >= 2 && xshrink == yshrink && xshrink >= 2 &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) && (inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1 baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimTolerance == 0
) { ) {
if (xshrink >= 8) { if (xshrink >= 8) {
xfactor = xfactor / 8; xfactor = xfactor / 8;
@@ -226,6 +241,12 @@ class PipelineWorker : public Nan::AsyncWorker {
shrink_on_load = 2; shrink_on_load = 2;
} }
} }
// Help ensure a final kernel-based reduction to prevent shrink aliasing
if (shrink_on_load > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
shrink_on_load = shrink_on_load / 2;
xfactor = xfactor * 2;
yfactor = yfactor * 2;
}
if (shrink_on_load > 1) { if (shrink_on_load > 1) {
// Reload input using shrink-on-load // Reload input using shrink-on-load
vips::VOption *option = VImage::option()->set("shrink", shrink_on_load); vips::VOption *option = VImage::option()->set("shrink", shrink_on_load);
@@ -269,6 +290,13 @@ class PipelineWorker : public Nan::AsyncWorker {
std::swap(xresidual, yresidual); std::swap(xresidual, yresidual);
} }
} }
// Help ensure a final kernel-based reduction to prevent shrink aliasing
if (xshrink > 1 && yshrink > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
xshrink = xshrink / 2;
yshrink = yshrink / 2;
xresidual = static_cast<double>(xshrink) / xfactor;
yresidual = static_cast<double>(yshrink) / yfactor;
}
// Ensure we're using a device-independent colour space // Ensure we're using a device-independent colour space
if (sharp::HasProfile(image)) { if (sharp::HasProfile(image)) {
@@ -277,8 +305,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.icc_transform( image = image.icc_transform(
const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option() const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option()
->set("embedded", TRUE) ->set("embedded", TRUE)
->set("intent", VIPS_INTENT_PERCEPTUAL) ->set("intent", VIPS_INTENT_PERCEPTUAL));
);
} catch(...) { } catch(...) {
// Ignore failure of embedded profile // Ignore failure of embedded profile
} }
@@ -286,8 +313,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.icc_transform( image = image.icc_transform(
const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option() const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option()
->set("input_profile", profileMap[VIPS_INTERPRETATION_CMYK].data()) ->set("input_profile", profileMap[VIPS_INTERPRETATION_CMYK].data())
->set("intent", VIPS_INTENT_PERCEPTUAL) ->set("intent", VIPS_INTENT_PERCEPTUAL));
);
} }
// Flatten image to remove alpha channel // Flatten image to remove alpha channel
@@ -301,8 +327,7 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->background[2] * multiplier baton->background[2] * multiplier
}; };
image = image.flatten(VImage::option() image = image.flatten(VImage::option()
->set("background", background) ->set("background", background));
);
} }
// Negate the colours in the image // Negate the colours in the image
@@ -320,13 +345,20 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.colourspace(VIPS_INTERPRETATION_B_W); image = image.colourspace(VIPS_INTERPRETATION_B_W);
} }
// Ensure image has an alpha channel when there is an overlay // Ensure image has an alpha channel when there is an overlay with an alpha channel
bool hasOverlay = baton->overlay != nullptr; VImage overlayImage;
if (hasOverlay && !HasAlpha(image)) { ImageType overlayImageType = ImageType::UNKNOWN;
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; bool shouldOverlayWithAlpha = FALSE;
image = image.bandjoin( if (baton->overlay != nullptr) {
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier) std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
); if (HasAlpha(overlayImage)) {
shouldOverlayWithAlpha = !baton->overlayCutout;
if (!HasAlpha(image)) {
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
}
}
} }
bool const shouldShrink = xshrink > 1 || yshrink > 1; bool const shouldShrink = xshrink > 1 || yshrink > 1;
@@ -334,9 +366,8 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldBlur = baton->blurSigma != 0.0; bool const shouldBlur = baton->blurSigma != 0.0;
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0; bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
bool const shouldSharpen = baton->sharpenSigma != 0.0; bool const shouldSharpen = baton->sharpenSigma != 0.0;
bool const shouldCutout = baton->overlayCutout;
bool const shouldPremultiplyAlpha = HasAlpha(image) && bool const shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen || (hasOverlay && !shouldCutout)); (shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen || shouldOverlayWithAlpha);
// Premultiply image alpha channel before all transformations to avoid // Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels // dark fringing around bright pixels
@@ -380,36 +411,42 @@ class PipelineWorker : public Nan::AsyncWorker {
// Perform kernel-based reduction // Perform kernel-based reduction
if (yresidual < 1.0 || xresidual < 1.0) { if (yresidual < 1.0 || xresidual < 1.0) {
VipsKernel kernel = static_cast<VipsKernel>( VipsKernel kernel = static_cast<VipsKernel>(
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()) vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
); if (
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) { kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
kernel != VIPS_KERNEL_LANCZOS3
) {
throw vips::VError("Unknown kernel"); throw vips::VError("Unknown kernel");
} }
if (yresidual < 1.0) { if (yresidual < 1.0) {
image = image.reducev(1.0 / yresidual, VImage::option() image = image.reducev(1.0 / yresidual, VImage::option()
->set("kernel", kernel) ->set("kernel", kernel)
->set("centre", baton->centreSampling) ->set("centre", baton->centreSampling));
);
} }
if (xresidual < 1.0) { if (xresidual < 1.0) {
image = image.reduceh(1.0 / xresidual, VImage::option() image = image.reduceh(1.0 / xresidual, VImage::option()
->set("kernel", kernel) ->set("kernel", kernel)
->set("centre", baton->centreSampling) ->set("centre", baton->centreSampling));
);
} }
} }
// Perform affine enlargement // Perform enlargement
if (yresidual > 1.0 || xresidual > 1.0) { if (yresidual > 1.0 || xresidual > 1.0) {
vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data()); if (trunc(xresidual) == xresidual && trunc(yresidual) == yresidual && baton->interpolator == "nearest") {
if (yresidual > 1.0) { // Fast, integral nearest neighbour enlargement
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option() image = image.zoom(static_cast<int>(xresidual), static_cast<int>(yresidual));
->set("interpolate", interpolator) } else {
); // Floating point affine transformation
} vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
if (xresidual > 1.0) { if (yresidual > 1.0 && xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option() image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator) ->set("interpolate", interpolator));
); } else if (yresidual > 1.0) {
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator));
} else if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator));
}
} }
} }
} }
@@ -433,13 +470,12 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Join additional color channels to the image // Join additional color channels to the image
if(baton->joinChannelIn.size() > 0) { if (baton->joinChannelIn.size() > 0) {
VImage joinImage; VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN; ImageType joinImageType = ImageType::UNKNOWN;
for(unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i], baton->accessMethod); std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i], baton->accessMethod);
image = image.bandjoin(joinImage); image = image.bandjoin(joinImage);
} }
image = image.copy(VImage::option()->set("interpretation", baton->colourspace)); image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
@@ -463,8 +499,8 @@ class PipelineWorker : public Nan::AsyncWorker {
background = { multiplier * ( background = { multiplier * (
0.2126 * baton->background[0] + 0.2126 * baton->background[0] +
0.7152 * baton->background[1] + 0.7152 * baton->background[1] +
0.0722 * baton->background[2] 0.0722 * baton->background[2])
)}; };
} }
// Add alpha channel to background colour // Add alpha channel to background colour
if (baton->background[3] < 255.0 || HasAlpha(image)) { if (baton->background[3] < 255.0 || HasAlpha(image)) {
@@ -475,45 +511,37 @@ class PipelineWorker : public Nan::AsyncWorker {
// Add non-transparent alpha channel, if required // Add non-transparent alpha channel, if required
if (baton->background[3] < 255.0 && !HasAlpha(image)) { if (baton->background[3] < 255.0 && !HasAlpha(image)) {
image = image.bandjoin( image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier) VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
);
} }
// Embed // Embed
int left = static_cast<int>(round((baton->width - image.width()) / 2)); int left = static_cast<int>(round((baton->width - image.width()) / 2));
int top = static_cast<int>(round((baton->height - image.height()) / 2)); int top = static_cast<int>(round((baton->height - image.height()) / 2));
image = image.embed(left, top, baton->width, baton->height, VImage::option() image = image.embed(left, top, baton->width, baton->height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND) ->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background) ->set("background", background));
);
} else if (baton->canvas != Canvas::IGNORE_ASPECT) { } else if (baton->canvas != Canvas::IGNORE_ASPECT) {
// Crop/max/min // Crop/max/min
int left;
int top;
if (baton->crop < 9) { if (baton->crop < 9) {
// Gravity-based crop // Gravity-based crop
int left;
int top;
std::tie(left, top) = sharp::CalculateCrop( std::tie(left, top) = sharp::CalculateCrop(
image.width(), image.height(), baton->width, baton->height, baton->crop image.width(), image.height(), baton->width, baton->height, baton->crop);
); int width = std::min(image.width(), baton->width);
} else if (baton->crop == 16) { int height = std::min(image.height(), baton->height);
// Entropy-based crop image = image.extract_area(left, top, width, height);
std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::EntropyStrategy());
} else { } else {
// Attention-based crop // Attention-based or Entropy-based crop
std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::AttentionStrategy()); image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->crop == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION));
} }
int width = std::min(image.width(), baton->width);
int height = std::min(image.height(), baton->height);
image = image.extract_area(left, top, width, height);
baton->cropCalcLeft = left;
baton->cropCalcTop = top;
} }
} }
// Post extraction // Post extraction
if (baton->topOffsetPost != -1) { if (baton->topOffsetPost != -1) {
image = image.extract_area( image = image.extract_area(
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
);
} }
// Extend edges // Extend edges
@@ -533,8 +561,8 @@ class PipelineWorker : public Nan::AsyncWorker {
background = { multiplier * ( background = { multiplier * (
0.2126 * baton->background[0] + 0.2126 * baton->background[0] +
0.7152 * baton->background[1] + 0.7152 * baton->background[1] +
0.0722 * baton->background[2] 0.0722 * baton->background[2])
)}; };
} }
// Add alpha channel to background colour // Add alpha channel to background colour
if (baton->background[3] < 255.0 || HasAlpha(image)) { if (baton->background[3] < 255.0 || HasAlpha(image)) {
@@ -545,8 +573,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Add non-transparent alpha channel, if required // Add non-transparent alpha channel, if required
if (baton->background[3] < 255.0 && !HasAlpha(image)) { if (baton->background[3] < 255.0 && !HasAlpha(image)) {
image = image.bandjoin( image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier) VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
);
} }
// Embed // Embed
baton->width = image.width() + baton->extendLeft + baton->extendRight; baton->width = image.width() + baton->extendLeft + baton->extendRight;
@@ -571,8 +598,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image = sharp::Convolve(image, image = sharp::Convolve(image,
baton->convKernelWidth, baton->convKernelHeight, baton->convKernelWidth, baton->convKernelHeight,
baton->convKernelScale, baton->convKernelOffset, baton->convKernelScale, baton->convKernelOffset,
baton->convKernel baton->convKernel);
);
} }
// Sharpen // Sharpen
@@ -581,10 +607,11 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Composite with overlay, if present // Composite with overlay, if present
if (hasOverlay) { if (baton->overlay != nullptr) {
VImage overlayImage; // Verify overlay image is within current dimensions
ImageType overlayImageType = ImageType::UNKNOWN; if (overlayImage.width() > image.width() || overlayImage.height() > image.height()) {
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod); throw vips::VError("Overlay image must have same dimensions or smaller");
}
// Check if overlay is tiled // Check if overlay is tiled
if (baton->overlayTile) { if (baton->overlayTile) {
int const overlayImageWidth = overlayImage.width(); int const overlayImageWidth = overlayImage.width();
@@ -606,48 +633,45 @@ class PipelineWorker : public Nan::AsyncWorker {
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area // the overlayX/YOffsets will now be used to CalculateCrop for extract_area
std::tie(left, top) = sharp::CalculateCrop( std::tie(left, top) = sharp::CalculateCrop(
overlayImage.width(), overlayImage.height(), image.width(), image.height(), overlayImage.width(), overlayImage.height(), image.width(), image.height(),
baton->overlayXOffset, baton->overlayYOffset baton->overlayXOffset, baton->overlayYOffset);
);
} else { } else {
// the overlayGravity will now be used to CalculateCrop for extract_area // the overlayGravity will now be used to CalculateCrop for extract_area
std::tie(left, top) = sharp::CalculateCrop( std::tie(left, top) = sharp::CalculateCrop(
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity);
);
} }
overlayImage = overlayImage.extract_area( overlayImage = overlayImage.extract_area(left, top, image.width(), image.height());
left, top, image.width(), image.height()
);
} }
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0 // the overlayGravity was used for extract_area, therefore set it back to its default value of 0
baton->overlayGravity = 0; baton->overlayGravity = 0;
} }
if (shouldCutout) { if (baton->overlayCutout) {
// 'cut out' the image, premultiplication is not required // 'cut out' the image, premultiplication is not required
image = sharp::Cutout(overlayImage, image, baton->overlayGravity); image = sharp::Cutout(overlayImage, image, baton->overlayGravity);
} else { } else {
// Ensure overlay has alpha channel // Ensure overlay is sRGB
if (!HasAlpha(overlayImage)) { overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB);
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0; // Ensure overlay matches premultiplication state
overlayImage = overlayImage.bandjoin( if (shouldPremultiplyAlpha) {
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier) // Ensure overlay has alpha channel
); if (!HasAlpha(overlayImage)) {
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
overlayImage = overlayImage.bandjoin(
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
}
overlayImage = overlayImage.premultiply();
} }
// Ensure image has alpha channel int left;
if (!HasAlpha(image)) { int top;
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
);
}
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) { if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
// Composite images with given offsets // Composite images at given offsets
image = sharp::Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset); std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
overlayImage.width(), overlayImage.height(), baton->overlayXOffset, baton->overlayYOffset);
} else { } else {
// Composite images with given gravity // Composite images with given gravity
image = sharp::Composite(overlayImage, image, baton->overlayGravity); std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
overlayImage.width(), overlayImage.height(), baton->overlayGravity);
} }
image = sharp::Composite(image, overlayImage, left, top);
} }
} }
@@ -661,6 +685,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.cast(VIPS_FORMAT_UCHAR); image = image.cast(VIPS_FORMAT_UCHAR);
} }
} }
baton->premultiplied = shouldPremultiplyAlpha;
// Gamma decoding (brighten) // Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) { if (baton->gamma >= 1 && baton->gamma <= 3) {
@@ -686,8 +711,8 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Extract an image channel (aka vips band) // Extract an image channel (aka vips band)
if(baton->extractChannel > -1) { if (baton->extractChannel > -1) {
if(baton->extractChannel >= image.bands()) { if (baton->extractChannel >= image.bands()) {
(baton->err).append("Cannot extract channel from image. Too few channels in image."); (baton->err).append("Cannot extract channel from image. Too few channels in image.");
return Error(); return Error();
} }
@@ -701,12 +726,9 @@ class PipelineWorker : public Nan::AsyncWorker {
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess // Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
// Transform colours from embedded profile to output profile // Transform colours from embedded profile to output profile
if (baton->withMetadata && if (baton->withMetadata && sharp::HasProfile(image) && profileMap[baton->colourspace] != std::string()) {
sharp::HasProfile(image) &&
profileMap[baton->colourspace] != std::string()) {
image = image.icc_transform(const_cast<char*>(profileMap[baton->colourspace].data()), image = image.icc_transform(const_cast<char*>(profileMap[baton->colourspace].data()),
VImage::option()->set("embedded", TRUE) VImage::option()->set("embedded", TRUE));
);
} }
} }
@@ -720,10 +742,11 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->width = image.width(); baton->width = image.width();
baton->height = image.height(); baton->height = image.height();
// Output // Output
if (baton->fileOut == "") { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@@ -732,31 +755,29 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", TRUE) ->set("optimize_coding", TRUE)));
));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
if(baton->colourspace == VIPS_INTERPRETATION_CMYK) { if (baton->colourspace == VIPS_INTERPRETATION_CMYK) {
baton->channels = std::min(baton->channels, 4); baton->channels = std::min(baton->channels, 4);
} else { } else {
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
// Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
// Strip profile // Strip profile
if (!baton->withMetadata) { if (!baton->withMetadata) {
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME); vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
} }
// Write PNG to buffer
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
->set("compression", baton->pngCompressionLevel) ->set("compression", baton->pngCompressionLevel)
->set("filter", baton->pngAdaptiveFiltering ? ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)));
VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE )
));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
area->free_fn = nullptr; area->free_fn = nullptr;
@@ -764,15 +785,41 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
)); ->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) {
// Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT);
}
VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor)
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) { } else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) {
// Write raw, uncompressed image data to buffer // Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
@@ -813,6 +860,7 @@ class PipelineWorker : public Nan::AsyncWorker {
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@@ -821,38 +869,49 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", TRUE) ->set("optimize_coding", TRUE));
);
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || isPng || (matchInput && } else if (baton->formatOut == "png" || isPng || (matchInput &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
// Write PNG to file
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
// Strip profile // Strip profile
if (!baton->withMetadata) { if (!baton->withMetadata) {
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME); vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
} }
// Write PNG to file
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
->set("compression", baton->pngCompressionLevel) ->set("compression", baton->pngCompressionLevel)
->set("filter", baton->pngAdaptiveFiltering ? ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE));
VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE )
);
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file // Write WEBP to file
AssertImageTypeDimensions(image, ImageType::WEBP);
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
); ->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) { } else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT);
}
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) ->set("squash", baton->tiffSquash)
); ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor)
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "dz" || isDz || isDzZip) { } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -870,7 +929,10 @@ class PipelineWorker : public Nan::AsyncWorker {
suffix = AssembleSuffixString(".png", options); suffix = AssembleSuffixString(".png", options);
} else if (baton->tileFormat == "webp") { } else if (baton->tileFormat == "webp") {
std::vector<std::pair<std::string, std::string>> options { std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->webpQuality)} {"Q", std::to_string(baton->webpQuality)},
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"}
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
} else { } else {
@@ -895,14 +957,12 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("overlap", baton->tileOverlap) ->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer) ->set("container", baton->tileContainer)
->set("layout", baton->tileLayout) ->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data())) ->set("suffix", const_cast<char*>(suffix.data())));
);
baton->formatOut = "dz"; baton->formatOut = "dz";
} else if (baton->formatOut == "v" || isV || (matchInput && inputImageType == ImageType::VIPS)) { } else if (baton->formatOut == "v" || isV || (matchInput && inputImageType == ImageType::VIPS)) {
// Write V to file // Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata));
);
baton->formatOut = "v"; baton->formatOut = "v";
} else { } else {
// Unsupported output format // Unsupported output format
@@ -918,7 +978,7 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback () { void HandleOKCallback() {
using Nan::New; using Nan::New;
using Nan::Set; using Nan::Set;
Nan::HandleScope(); Nan::HandleScope();
@@ -944,6 +1004,7 @@ class PipelineWorker : public Nan::AsyncWorker {
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width))); Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height))); Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels))); Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied));
if (baton->cropCalcLeft != -1 && baton->cropCalcLeft != -1) { if (baton->cropCalcLeft != -1 && baton->cropCalcLeft != -1) {
Set(info, New("cropCalcLeft").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcLeft))); Set(info, New("cropCalcLeft").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcLeft)));
Set(info, New("cropCalcTop").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcTop))); Set(info, New("cropCalcTop").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcTop)));
@@ -952,8 +1013,8 @@ class PipelineWorker : public Nan::AsyncWorker {
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Pass ownership of output data to Buffer instance // Pass ownership of output data to Buffer instance
argv[1] = Nan::NewBuffer( argv[1] = Nan::NewBuffer(
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr)
).ToLocalChecked(); .ToLocalChecked();
// Add buffer size to info // Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength))); Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
argv[2] = info; argv[2] = info;
@@ -972,18 +1033,24 @@ class PipelineWorker : public Nan::AsyncWorker {
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t { [this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index); GetFromPersistent(index);
return index + 1; return index + 1;
} });
);
delete baton->input; delete baton->input;
delete baton->overlay; delete baton->overlay;
delete baton->boolean; delete baton->boolean;
for_each(baton->joinChannelIn.begin(), baton->joinChannelIn.end(), for_each(baton->joinChannelIn.begin(), baton->joinChannelIn.end(),
[this](sharp::InputDescriptor *joinChannelIn) { [this](sharp::InputDescriptor *joinChannelIn) {
delete joinChannelIn; delete joinChannelIn;
} });
);
delete baton; delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message);
warning = sharp::VipsWarningPop();
}
// Decrement processing task counter // Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess); g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) }; v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
@@ -996,43 +1063,48 @@ class PipelineWorker : public Nan::AsyncWorker {
private: private:
PipelineBaton *baton; PipelineBaton *baton;
Nan::Callback *debuglog;
Nan::Callback *queueListener; Nan::Callback *queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist; std::vector<v8::Local<v8::Object>> buffersToPersist;
/* /*
Calculate the angle of rotation and need-to-flip for the output image. Calculate the angle of rotation and need-to-flip for the given Exif orientation
In order of priority: By default, returns zero, i.e. no rotation.
1. Use explicitly requested angle (supports 90, 180, 270)
2. Use input image EXIF Orientation header - supports mirroring
3. Otherwise default to zero, i.e. no rotation
*/ */
std::tuple<VipsAngle, bool, bool> std::tuple<VipsAngle, bool, bool>
CalculateRotationAndFlip(int const angle, vips::VImage image) { CalculateExifRotationAndFlip(int const exifOrientation) {
VipsAngle rotate = VIPS_ANGLE_D0; VipsAngle rotate = VIPS_ANGLE_D0;
bool flip = FALSE; bool flip = FALSE;
bool flop = FALSE; bool flop = FALSE;
if (angle == -1) { switch (exifOrientation) {
switch(sharp::ExifOrientation(image)) { case 6: rotate = VIPS_ANGLE_D90; break;
case 6: rotate = VIPS_ANGLE_D90; break; case 3: rotate = VIPS_ANGLE_D180; break;
case 3: rotate = VIPS_ANGLE_D180; break; case 8: rotate = VIPS_ANGLE_D270; break;
case 8: rotate = VIPS_ANGLE_D270; break; case 2: flop = TRUE; break; // flop 1
case 2: flop = TRUE; break; // flop 1 case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6 case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3 case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
} else {
if (angle == 90) {
rotate = VIPS_ANGLE_D90;
} else if (angle == 180) {
rotate = VIPS_ANGLE_D180;
} else if (angle == 270) {
rotate = VIPS_ANGLE_D270;
}
} }
return std::make_tuple(rotate, flip, flop); return std::make_tuple(rotate, flip, flop);
} }
/*
Calculate the rotation for the given angle.
Supports any positive or negative angle that is a multiple of 90.
*/
VipsAngle
CalculateAngleRotation(int angle) {
angle = angle % 360;
if (angle < 0)
angle = 360 + angle;
switch (angle) {
case 90: return VIPS_ANGLE_D90;
case 180: return VIPS_ANGLE_D180;
case 270: return VIPS_ANGLE_D270;
}
return VIPS_ANGLE_D0;
}
/* /*
Assemble the suffix argument to dzsave, which is the format (by extname) Assemble the suffix argument to dzsave, which is the format (by extname)
alongisde comma-separated arguments to the corresponding `formatsave` vips alongisde comma-separated arguments to the corresponding `formatsave` vips
@@ -1114,7 +1186,7 @@ NAN_METHOD(pipeline) {
// Background colour // Background colour
v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background"); v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background");
for (unsigned int i = 0; i < 4; i++) { for (unsigned int i = 0; i < 4; i++) {
baton->background[i] = AttrTo<uint32_t>(background, i); baton->background[i] = AttrTo<double>(background, i);
} }
// Overlay options // Overlay options
if (HasAttr(options, "overlay")) { if (HasAttr(options, "overlay")) {
@@ -1132,12 +1204,12 @@ NAN_METHOD(pipeline) {
baton->interpolator = AttrAsStr(options, "interpolator"); baton->interpolator = AttrAsStr(options, "interpolator");
baton->centreSampling = AttrTo<bool>(options, "centreSampling"); baton->centreSampling = AttrTo<bool>(options, "centreSampling");
// Join Channel Options // Join Channel Options
if(HasAttr(options, "joinChannelIn")) { if (HasAttr(options, "joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked()) v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked())
.ToLocalChecked().As<v8::Object>(); .ToLocalChecked().As<v8::Object>();
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>(); v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length"); int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for(int i = 0; i < joinChannelArrayLength; i++) { for (int i = 0; i < joinChannelArrayLength; i++) {
baton->joinChannelIn.push_back( baton->joinChannelIn.push_back(
CreateInputDescriptor( CreateInputDescriptor(
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(), Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
@@ -1154,12 +1226,10 @@ NAN_METHOD(pipeline) {
baton->threshold = AttrTo<int32_t>(options, "threshold"); baton->threshold = AttrTo<int32_t>(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale"); baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance"); baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance");
if(baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
}
baton->gamma = AttrTo<double>(options, "gamma"); baton->gamma = AttrTo<double>(options, "gamma");
baton->greyscale = AttrTo<bool>(options, "greyscale"); baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise"); baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle"); baton->angle = AttrTo<int32_t>(options, "angle");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip"); baton->flip = AttrTo<bool>(options, "flip");
@@ -1209,7 +1279,21 @@ NAN_METHOD(pipeline) {
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel"); baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering"); baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality"); baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
baton->tiffXres = AttrTo<double>(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres");
// tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data()));
// Tile output // Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize"); baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
@@ -1228,13 +1312,22 @@ NAN_METHOD(pipeline) {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ; baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
} }
baton->tileFormat = AttrAsStr(options, "tileFormat"); baton->tileFormat = AttrAsStr(options, "tileFormat");
// Force random access for certain operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
baton->trimTolerance != 0 || baton->normalise ||
baton->crop == 16 || baton->crop == 17)) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
}
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog"));
// Function to notify of queue length changes // Function to notify of queue length changes
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener")); Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist)); Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);

View File

@@ -1,12 +1,28 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_PIPELINE_H_ #ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_ #define SRC_PIPELINE_H_
#include <memory> #include <memory>
#include <string>
#include <vector>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "nan.h" #include "./common.h"
#include "common.h"
NAN_METHOD(pipeline); NAN_METHOD(pipeline);
@@ -48,6 +64,7 @@ struct PipelineBaton {
int crop; int crop;
int cropCalcLeft; int cropCalcLeft;
int cropCalcTop; int cropCalcTop;
bool premultiplied;
std::string kernel; std::string kernel;
std::string interpolator; std::string interpolator;
bool centreSampling; bool centreSampling;
@@ -64,6 +81,7 @@ struct PipelineBaton {
double gamma; double gamma;
bool greyscale; bool greyscale;
bool normalise; bool normalise;
bool useExifOrientation;
int angle; int angle;
bool rotateBeforePreExtract; bool rotateBeforePreExtract;
bool flip; bool flip;
@@ -84,7 +102,15 @@ struct PipelineBaton {
int pngCompressionLevel; int pngCompressionLevel;
bool pngAdaptiveFiltering; bool pngAdaptiveFiltering;
int webpQuality; int webpQuality;
int webpAlphaQuality;
bool webpNearLossless;
bool webpLossless;
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
bool tiffSquash;
double tiffXres;
double tiffYres;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
@@ -121,6 +147,7 @@ struct PipelineBaton {
crop(0), crop(0),
cropCalcLeft(-1), cropCalcLeft(-1),
cropCalcTop(-1), cropCalcTop(-1),
premultiplied(false),
centreSampling(false), centreSampling(false),
flatten(false), flatten(false),
negate(false), negate(false),
@@ -134,6 +161,7 @@ struct PipelineBaton {
gamma(0.0), gamma(0.0),
greyscale(false), greyscale(false),
normalise(false), normalise(false),
useExifOrientation(false),
angle(0), angle(0),
flip(false), flip(false),
flop(false), flop(false),
@@ -153,6 +181,11 @@ struct PipelineBaton {
pngAdaptiveFiltering(true), pngAdaptiveFiltering(true),
webpQuality(80), webpQuality(80),
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE),
tiffSquash(false),
tiffXres(1.0),
tiffYres(1.0),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
convKernelWidth(0), convKernelWidth(0),

View File

@@ -1,7 +1,20 @@
#include <node.h> // Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
#include <vips/vips8> //
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nan.h" #include <node.h>
#include <nan.h>
#include <vips/vips8>
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
@@ -11,6 +24,9 @@
NAN_MODULE_INIT(init) { NAN_MODULE_INIT(init) {
vips_init("sharp"); vips_init("sharp");
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());

View File

@@ -1,10 +1,25 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cmath> #include <cmath>
#include <string>
#include <node.h> #include <node.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include <vips/vector.h> #include <vips/vector.h>
#include "nan.h"
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
#include "utilities.h" #include "utilities.h"
@@ -45,14 +60,11 @@ NAN_METHOD(cache) {
// Get memory stats // Get memory stats
Local<Object> memory = New<Object>(); Local<Object> memory = New<Object>();
Set(memory, New("current").ToLocalChecked(), Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576))) New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576))));
);
Set(memory, New("high").ToLocalChecked(), Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))) New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))));
);
Set(memory, New("max").ToLocalChecked(), Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))) New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))));
);
// Get file stats // Get file stats
Local<Object> files = New<Object>(); Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files())); Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));

View File

@@ -1,7 +1,21 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_
#include "nan.h" #include <nan.h>
NAN_METHOD(cache); NAN_METHOD(cache);
NAN_METHOD(concurrency); NAN_METHOD(concurrency);

View File

@@ -5,17 +5,18 @@
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"description": "Benchmark and performance tests for sharp", "description": "Benchmark and performance tests for sharp",
"scripts": { "scripts": {
"test": "VIPS_WARNING=0 node perf && node random && node parallel" "test": "node perf && node random && node parallel"
}, },
"devDependencies": { "devDependencies": {
"async": "^2.1.4", "async": "^2.5.0",
"benchmark": "^2.1.2", "benchmark": "^2.1.4",
"gm": "^1.23.0", "gm": "^1.23.0",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.3", "imagemagick-native": "^1.9.3",
"jimp": "^0.2.27", "images": "^3.0.0",
"lwip": "^0.0.9", "jimp": "^0.2.28",
"mapnik": "^3.5.14", "mapnik": "^3.6.2",
"pajk-lwip": "^0.2.0",
"semver": "^5.3.0" "semver": "^5.3.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@@ -7,11 +7,17 @@ const assert = require('assert');
const Benchmark = require('benchmark'); const Benchmark = require('benchmark');
// Contenders // Contenders
const sharp = require('../../');
const gm = require('gm'); const gm = require('gm');
const imagemagick = require('imagemagick'); const imagemagick = require('imagemagick');
const mapnik = require('mapnik'); const mapnik = require('mapnik');
const jimp = require('jimp'); const jimp = require('jimp');
const sharp = require('../../'); let images;
try {
images = require('images');
} catch (err) {
console.log('Excluding node-images');
}
let imagemagickNative; let imagemagickNative;
try { try {
imagemagickNative = require('imagemagick-native'); imagemagickNative = require('imagemagick-native');
@@ -20,7 +26,7 @@ try {
} }
let lwip; let lwip;
try { try {
lwip = require('lwip'); lwip = require('pajk-lwip');
} catch (err) { } catch (err) {
console.log('Excluding lwip'); console.log('Excluding lwip');
} }
@@ -145,7 +151,7 @@ async.series({
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.fromBytes(inputJpgBuffer, function (err, img) { mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) {
if (err) throw err; if (err) throw err;
img img
.resize(width, height, { .resize(width, height, {
@@ -266,6 +272,14 @@ async.series({
}); });
} }
}); });
// images
if (typeof images !== 'undefined') {
jpegSuite.add('images-file-file', function () {
images(fixtures.inputJpg)
.resize(width, height)
.save(fixtures.outputJpg, { quality: 80 });
});
}
// sharp // sharp
jpegSuite.add('sharp-buffer-file', { jpegSuite.add('sharp-buffer-file', {
defer: true, defer: true,
@@ -733,7 +747,7 @@ async.series({
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.fromBytes(inputPngBuffer, function (err, img) { mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, function (err, img) {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply(function (err, img) {
if (err) throw err; if (err) throw err;
@@ -819,6 +833,14 @@ async.series({
}); });
} }
}); });
// images
if (typeof images !== 'undefined') {
pngSuite.add('images-file-file', function () {
images(fixtures.inputPng)
.resize(width, height)
.save(fixtures.outputPng);
});
}
// sharp // sharp
pngSuite.add('sharp-buffer-file', { pngSuite.add('sharp-buffer-file', {
defer: true, defer: true,
@@ -957,7 +979,7 @@ async.series({
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputWebp) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {

View File

@@ -15,7 +15,7 @@ const min = 320;
const max = 960; const max = 960;
const randomDimension = function () { const randomDimension = function () {
return Math.ceil(Math.random() * (max - min) + min); return Math.ceil((Math.random() * (max - min)) + min);
}; };
new Benchmark.Suite('random').add('imagemagick', { new Benchmark.Suite('random').add('imagemagick', {

BIN
test/fixtures/320x240.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
test/fixtures/8bit_depth.tiff vendored Normal file

Binary file not shown.

BIN
test/fixtures/alpha-layer-2-ink.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
test/fixtures/expected/create-rgb.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

BIN
test/fixtures/expected/create-rgba.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -25,8 +25,8 @@ const fingerprint = function (image, callback) {
let fingerprint = ''; let fingerprint = '';
for (let col = 0; col < 8; col++) { for (let col = 0; col < 8; col++) {
for (let row = 0; row < 8; row++) { for (let row = 0; row < 8; row++) {
const left = data[row * 8 + col]; const left = data[(row * 8) + col];
const right = data[row * 8 + col + 1]; const right = data[(row * 8) + col + 1];
fingerprint = fingerprint + (left < right ? '1' : '0'); fingerprint = fingerprint + (left < right ? '1' : '0');
} }
} }
@@ -64,6 +64,8 @@ module.exports = {
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'), inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/ inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
inputJpgLarge: getPath('giant-image.jpg'), inputJpgLarge: getPath('giant-image.jpg'),
inputJpg320x240: getPath('320x240.jpg'), // http://www.andrewault.net/2010/01/26/create-a-test-pattern-video-with-perl/
inputJpgOverlayLayer2: getPath('alpha-layer-2-ink.jpg'),
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
@@ -84,6 +86,8 @@ module.exports = {
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
@@ -102,6 +106,7 @@ module.exports = {
outputPng: getPath('output.png'), outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'), outputWebP: getPath('output.webp'),
outputV: getPath('output.v'), outputV: getPath('output.v'),
outputTiff: getPath('output.tiff'),
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
// Path for tests requiring human inspection // Path for tests requiring human inspection

BIN
test/fixtures/uncompressed_tiff.tiff vendored Normal file

Binary file not shown.

View File

@@ -1,48 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const cpplint = require('node-cpplint/lib/');
describe('cpplint', function () {
// Ignore cpplint failures, possibly newline-related, on Windows
if (process.platform !== 'win32') {
// List C++ source files
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).filter(function (source) {
return source !== 'libvips';
}).forEach(function (source) {
const file = path.join('src', source);
it(file, function (done) {
// Lint each source file
cpplint({
files: [file],
linelength: 120,
filters: {
legal: {
copyright: false
},
build: {
include: false
},
whitespace: {
parens: false
},
runtime: {
indentation_namespace: false
}
}
}, function (err, report) {
if (err) {
throw err;
}
const expected = {};
expected[file] = [];
assert.deepEqual(expected, report);
done();
});
});
});
}
});

View File

@@ -161,7 +161,7 @@ describe('Crop', function () {
describe('Entropy-based strategy', function () { describe('Entropy-based strategy', function () {
it('JPEG', function (done) { it('JPEG', function (done) {
sharp(fixtures.inputJpgWithCmykProfile) sharp(fixtures.inputJpg)
.resize(80, 320) .resize(80, 320)
.crop(sharp.strategy.entropy) .crop(sharp.strategy.entropy)
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
@@ -170,9 +170,7 @@ 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);
assert.strictEqual(250, info.cropCalcLeft); fixtures.assertSimilar(fixtures.expected('crop-strategy-entropy.jpg'), data, done);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
}); });
}); });
@@ -186,8 +184,20 @@ 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);
assert.strictEqual(0, info.cropCalcLeft); fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
assert.strictEqual(80, info.cropCalcTop); });
});
it('supports the strategy passed as a string', function (done) {
sharp(fixtures.inputPngWithTransparency)
.resize(320, 80)
.crop('entropy')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
}); });
}); });
@@ -195,7 +205,7 @@ describe('Crop', function () {
describe('Attention strategy', function () { describe('Attention strategy', function () {
it('JPEG', function (done) { it('JPEG', function (done) {
sharp(fixtures.inputJpgWithCmykProfile) sharp(fixtures.inputJpg)
.resize(80, 320) .resize(80, 320)
.crop(sharp.strategy.attention) .crop(sharp.strategy.attention)
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
@@ -204,9 +214,7 @@ 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);
assert.strictEqual(250, info.cropCalcLeft); fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
}); });
}); });
@@ -220,8 +228,20 @@ 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);
assert.strictEqual(0, info.cropCalcLeft); fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
assert.strictEqual(80, info.cropCalcTop); });
});
it('supports the strategy passed as a string', function (done) {
sharp(fixtures.inputPngWithTransparency)
.resize(320, 80)
.crop('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);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
}); });
}); });

View File

@@ -8,6 +8,7 @@ const fixtures = require('../fixtures');
describe('Interpolators and kernels', function () { describe('Interpolators and kernels', function () {
describe('Reducers', function () { describe('Reducers', function () {
[ [
sharp.kernel.nearest,
sharp.kernel.cubic, sharp.kernel.cubic,
sharp.kernel.lanczos2, sharp.kernel.lanczos2,
sharp.kernel.lanczos3 sharp.kernel.lanczos3
@@ -34,17 +35,54 @@ describe('Interpolators and kernels', function () {
sharp.interpolator.locallyBoundedBicubic, sharp.interpolator.locallyBoundedBicubic,
sharp.interpolator.vertexSplitQuadraticBasisSpline sharp.interpolator.vertexSplitQuadraticBasisSpline
].forEach(function (interpolator) { ].forEach(function (interpolator) {
it(interpolator, function (done) { describe(interpolator, function () {
sharp(fixtures.inputJpg) it('x and y', function (done) {
.resize(320, null, { interpolator: interpolator }) sharp(fixtures.inputTiff8BitDepth)
.toBuffer(function (err, data, info) { .resize(200, 200, { interpolator: interpolator })
if (err) throw err; .png()
assert.strictEqual('jpeg', info.format); .toBuffer(function (err, data, info) {
assert.strictEqual(320, info.width); if (err) throw err;
fixtures.assertSimilar(fixtures.inputJpg, data, done); assert.strictEqual(200, info.width);
}); assert.strictEqual(200, info.height);
done();
});
});
it('x only', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(200, 21, { interpolator: interpolator })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(21, info.height);
done();
});
});
it('y only', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(21, 200, { interpolator: interpolator })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(21, info.width);
assert.strictEqual(200, info.height);
done();
});
});
}); });
}); });
it('nearest with integral factor', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(210, 210, { interpolator: 'nearest' })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(210, info.width);
assert.strictEqual(210, info.height);
done();
});
});
}); });
it('unknown kernel throws', function () { it('unknown kernel throws', function () {

View File

@@ -77,16 +77,58 @@ describe('Input/output', function () {
readable.pipe(pipeline); readable.pipe(pipeline);
}); });
it('Read from Stream and write to Buffer via Promise', function (done) { it('Read from Stream and write to Buffer via Promise resolved with Buffer', function () {
const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().resize(1, 1); const pipeline = sharp().resize(1, 1);
pipeline.toBuffer().then(function (data) { fs.createReadStream(fixtures.inputJpg).pipe(pipeline);
assert.strictEqual(true, data.length > 0); return pipeline
done(); .toBuffer({resolveWithObject: false})
}).catch(function (err) { .then(function (data) {
throw err; assert.strictEqual(true, data instanceof Buffer);
}); assert.strictEqual(true, data.length > 0);
readable.pipe(pipeline); });
});
it('Read from Stream and write to Buffer via Promise resolved with Object', function () {
const pipeline = sharp().resize(1, 1);
fs.createReadStream(fixtures.inputJpg).pipe(pipeline);
return pipeline
.toBuffer({resolveWithObject: true})
.then(function (object) {
assert.strictEqual('object', typeof object);
assert.strictEqual('object', typeof object.info);
assert.strictEqual('jpeg', object.info.format);
assert.strictEqual(1, object.info.width);
assert.strictEqual(1, object.info.height);
assert.strictEqual(3, object.info.channels);
assert.strictEqual(true, object.data instanceof Buffer);
assert.strictEqual(true, object.data.length > 0);
});
});
it('Read from File and write to Buffer via Promise resolved with Buffer', function () {
return sharp(fixtures.inputJpg)
.resize(1, 1)
.toBuffer({resolveWithObject: false})
.then(function (data) {
assert.strictEqual(true, data instanceof Buffer);
assert.strictEqual(true, data.length > 0);
});
});
it('Read from File and write to Buffer via Promise resolved with Object', function () {
return sharp(fixtures.inputJpg)
.resize(1, 1)
.toBuffer({resolveWithObject: true})
.then(function (object) {
assert.strictEqual('object', typeof object);
assert.strictEqual('object', typeof object.info);
assert.strictEqual('jpeg', object.info.format);
assert.strictEqual(1, object.info.width);
assert.strictEqual(1, object.info.height);
assert.strictEqual(3, object.info.channels);
assert.strictEqual(true, object.data instanceof Buffer);
assert.strictEqual(true, object.data.length > 0);
});
}); });
it('Read from Stream and write to Stream', function (done) { it('Read from Stream and write to Stream', function (done) {
@@ -157,6 +199,28 @@ describe('Input/output', function () {
readableButNotAnImage.pipe(writable); readableButNotAnImage.pipe(writable);
}); });
it('Readable side of Stream can start flowing after Writable side has finished', function (done) {
const readable = fs.createReadStream(fixtures.inputJpg);
const writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function () {
sharp(fixtures.outputJpg).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fs.unlinkSync(fixtures.outputJpg);
done();
});
});
const pipeline = sharp().resize(320, 240);
readable.pipe(pipeline);
pipeline.on('finish', function () {
pipeline.pipe(writable);
});
});
it('Sequential read, force JPEG', function (done) { it('Sequential read, force JPEG', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.sequentialRead() .sequentialRead()
@@ -189,6 +253,21 @@ describe('Input/output', function () {
}); });
}); });
it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng)
.resize(320, 240)
.toFormat('jpg')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Fail when output File is input File', function (done) { it('Fail when output File is input File', function (done) {
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) { sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
assert(!!err); assert(!!err);
@@ -224,7 +303,7 @@ describe('Input/output', function () {
}); });
it('Fail when input is empty Buffer', function (done) { it('Fail when input is empty Buffer', function (done) {
sharp(new Buffer(0)).toBuffer().then(function () { sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false); assert(false);
done(); done();
}).catch(function (err) { }).catch(function (err) {
@@ -234,7 +313,7 @@ describe('Input/output', function () {
}); });
it('Fail when input is invalid Buffer', function (done) { it('Fail when input is invalid Buffer', function (done) {
sharp(new Buffer([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () { sharp(Buffer.from([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () {
assert(false); assert(false);
done(); done();
}).catch(function (err) { }).catch(function (err) {
@@ -244,6 +323,16 @@ describe('Input/output', function () {
}); });
describe('Fail for unsupported input', function () { describe('Fail for unsupported input', function () {
it('Undefined', function () {
assert.throws(function () {
sharp(undefined);
});
});
it('Null', function () {
assert.throws(function () {
sharp(null);
});
});
it('Numeric', function () { it('Numeric', function () {
assert.throws(function () { assert.throws(function () {
sharp(1); sharp(1);
@@ -254,11 +343,6 @@ describe('Input/output', function () {
sharp(true); sharp(true);
}); });
}); });
it('Empty Object', function () {
assert.throws(function () {
sharp({});
});
});
it('Error Object', function () { it('Error Object', function () {
assert.throws(function () { assert.throws(function () {
sharp(new Error()); sharp(new Error());
@@ -372,6 +456,50 @@ describe('Input/output', function () {
done(); done();
}); });
}); });
it('should work for webp alpha quality', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({alphaQuality: 80})
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
fixtures.assertSimilar(fixtures.expected('webp-alpha-80.webp'), data, done);
});
});
it('should work for webp lossless', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({lossless: true})
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
fixtures.assertSimilar(fixtures.expected('webp-lossless.webp'), data, done);
});
});
it('should work for webp near-lossless', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({nearLossless: true, quality: 50})
.toBuffer(function (err50, data50, info50) {
if (err50) throw err50;
assert.strictEqual(true, data50.length > 0);
assert.strictEqual('webp', info50.format);
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
});
});
it('should use near-lossless when both lossless and nearLossless are specified', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({nearLossless: true, quality: 50, lossless: true})
.toBuffer(function (err50, data50, info50) {
if (err50) throw err50;
assert.strictEqual(true, data50.length > 0);
assert.strictEqual('webp', info50.format);
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
});
});
} }
it('Invalid output format', function (done) { it('Invalid output format', function (done) {
@@ -729,12 +857,32 @@ describe('Input/output', function () {
}); });
}); });
it('Save TIFF to Buffer', function (done) {
sharp(fixtures.inputTiff)
.resize(320, 240)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Invalid WebP quality throws error', function () { it('Invalid WebP quality throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().webp({ quality: 101 }); sharp().webp({ quality: 101 });
}); });
}); });
it('Invalid WebP alpha quality throws error', function () {
assert.throws(function () {
sharp().webp({ alphaQuality: 101 });
});
});
it('Invalid TIFF quality throws error', function () { it('Invalid TIFF quality throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().tiff({ quality: 101 }); sharp().tiff({ quality: 101 });
@@ -747,6 +895,219 @@ describe('Input/output', function () {
}); });
}); });
it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) {
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images
.tiff({
squash: false,
compression: 'none'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size === startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) {
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images
.tiff({
squash: true,
compression: 'none'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < (startSize / 2));
fs.unlink(fixtures.outputTiff, done);
});
});
it('Invalid TIFF squash value throws error', function () {
assert.throws(function () {
sharp().tiff({ squash: 'true' });
});
});
it('TIFF setting xres and yres on file', function (done) {
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff)
.tiff({
xres: (res),
yres: (res)
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
sharp(fixtures.outputTiff).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
fs.unlink(fixtures.outputTiff, done);
});
});
});
it('TIFF setting xres and yres on buffer', function (done) {
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff)
.tiff({
xres: (res),
yres: (res)
})
.toBuffer(function (err, data, info) {
if (err) throw err;
sharp(data).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
done();
});
});
});
it('TIFF invalid xres value should throw an error', function () {
assert.throws(function () {
sharp().tiff({ xres: '1000.0' });
});
});
it('TIFF invalid yres value should throw an error', function () {
assert.throws(function () {
sharp().tiff({ yres: '1000.0' });
});
});
it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'lzw',
predictor: 'horizontal'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'deflate',
predictor: 'horizontal'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('TIFF deflate compression with float predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'deflate',
predictor: 'float'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('TIFF deflate compression without predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'deflate',
predictor: 'none'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('TIFF jpeg compression shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'jpeg'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('TIFF none compression does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ compression: 'none' });
});
});
it('TIFF lzw compression does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ compression: 'lzw' });
});
});
it('TIFF deflate compression does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ compression: 'deflate' });
});
});
it('TIFF invalid compression option throws', function () {
assert.throws(function () {
sharp().tiff({ compression: 0 });
});
});
it('TIFF invalid compression option throws', function () {
assert.throws(function () {
sharp().tiff({ compression: 'a' });
});
});
it('TIFF invalid predictor option throws', function () {
assert.throws(function () {
sharp().tiff({ predictor: 'a' });
});
});
it('TIFF horizontal predictor does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ predictor: 'horizontal' });
});
});
it('TIFF float predictor does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ predictor: 'float' });
});
});
it('TIFF none predictor does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ predictor: 'none' });
});
});
it('Input and output formats match when not forcing', function (done) { it('Input and output formats match when not forcing', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
@@ -930,7 +1291,7 @@ describe('Input/output', function () {
sharp(fixtures.inputJpg).metadata(function (err, metadata) { sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err; if (err) throw err;
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height - 1) .limitInputPixels((metadata.width * metadata.height) - 1)
.toBuffer(function (err) { .toBuffer(function (err) {
assert.strictEqual(true, !!err); assert.strictEqual(true, !!err);
done(); done();
@@ -963,27 +1324,27 @@ describe('Input/output', function () {
describe('Raw pixel input', function () { describe('Raw pixel input', function () {
it('Missing options', function () { it('Missing options', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { raw: {} }); sharp({ raw: {} });
}); });
}); });
it('Incomplete options', function () { it('Incomplete options', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { raw: { width: 1, height: 1 } }); sharp({ raw: { width: 1, height: 1 } });
}); });
}); });
it('Invalid channels', function () { it('Invalid channels', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { raw: { width: 1, height: 1, channels: 5 } }); sharp({ raw: { width: 1, height: 1, channels: 5 } });
}); });
}); });
it('Invalid height', function () { it('Invalid height', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { raw: { width: 1, height: 0, channels: 4 } }); sharp({ raw: { width: 1, height: 0, channels: 4 } });
}); });
}); });
it('Invalid width', function () { it('Invalid width', function () {
assert.throws(function () { assert.throws(function () {
sharp(null, { raw: { width: 'zoinks', height: 1, channels: 4 } }); sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } });
}); });
}); });
it('RGB', function (done) { it('RGB', function (done) {
@@ -1036,12 +1397,72 @@ describe('Input/output', function () {
assert.strictEqual(256, info.width); assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height); assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, done); fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, { threshold: 7 }, done);
}); });
}); });
}); });
}); });
describe('create new image', function () {
it('RGB', function (done) {
const create = {
width: 10,
height: 20,
channels: 3,
background: { r: 0, g: 255, b: 0 }
};
sharp({ create: create })
.jpeg()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(create.width, info.width);
assert.strictEqual(create.height, info.height);
assert.strictEqual(create.channels, info.channels);
assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('create-rgb.jpg'), data, done);
});
});
it('RGBA', function (done) {
const create = {
width: 20,
height: 10,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 128 }
};
sharp({ create: create })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(create.width, info.width);
assert.strictEqual(create.height, info.height);
assert.strictEqual(create.channels, info.channels);
assert.strictEqual('png', info.format);
fixtures.assertSimilar(fixtures.expected('create-rgba.png'), data, done);
});
});
it('Invalid channels', function () {
const create = {
width: 10,
height: 20,
channels: 2,
background: { r: 0, g: 0, b: 0 }
};
assert.throws(function () {
sharp({ create: create });
});
});
it('Missing background', function () {
const create = {
width: 10,
height: 20,
channels: 3
};
assert.throws(function () {
sharp({ create: create });
});
});
});
it('Queue length change events', function (done) { it('Queue length change events', function (done) {
let eventCounter = 0; let eventCounter = 0;
const queueListener = function (queueLength) { const queueListener = function (queueLength) {

View File

@@ -17,6 +17,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -35,6 +36,7 @@ describe('Image metadata', function () {
assert.strictEqual(600, metadata.height); assert.strictEqual(600, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density); assert.strictEqual(72, metadata.density);
assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -56,25 +58,24 @@ describe('Image metadata', function () {
}); });
}); });
if (sharp.format.tiff.input.file) { it('TIFF', function (done) {
it('TIFF', function (done) { sharp(fixtures.inputTiff).metadata(function (err, metadata) {
sharp(fixtures.inputTiff).metadata(function (err, metadata) { if (err) throw err;
if (err) throw err; assert.strictEqual('tiff', metadata.format);
assert.strictEqual('tiff', metadata.format); assert.strictEqual(2464, metadata.width);
assert.strictEqual(2464, metadata.width); assert.strictEqual(3248, metadata.height);
assert.strictEqual(3248, metadata.height); assert.strictEqual('b-w', metadata.space);
assert.strictEqual('b-w', metadata.space); assert.strictEqual(1, metadata.channels);
assert.strictEqual(1, metadata.channels); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(300, metadata.density); assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(1, metadata.orientation); assert.strictEqual(1, metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
done(); done();
});
}); });
} });
it('PNG', function (done) { it('PNG', function (done) {
sharp(fixtures.inputPng).metadata(function (err, metadata) { sharp(fixtures.inputPng).metadata(function (err, metadata) {
@@ -84,6 +85,7 @@ describe('Image metadata', function () {
assert.strictEqual(2074, metadata.height); assert.strictEqual(2074, metadata.height);
assert.strictEqual('b-w', metadata.space); assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels); assert.strictEqual(1, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(300, metadata.density); assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -102,6 +104,7 @@ describe('Image metadata', function () {
assert.strictEqual(1536, metadata.height); assert.strictEqual(1536, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(4, metadata.channels); assert.strictEqual(4, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density); assert.strictEqual(72, metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual(true, metadata.hasAlpha);
@@ -112,80 +115,59 @@ describe('Image metadata', function () {
}); });
}); });
if (sharp.format.webp.input.file) { it('WebP', function (done) {
it('WebP', function (done) { sharp(fixtures.inputWebP).metadata(function (err, metadata) {
sharp(fixtures.inputWebP).metadata(function (err, metadata) { if (err) throw err;
if (err) throw err; assert.strictEqual('webp', metadata.format);
assert.strictEqual('webp', metadata.format); assert.strictEqual(1024, metadata.width);
assert.strictEqual(1024, metadata.width); assert.strictEqual(772, metadata.height);
assert.strictEqual(772, metadata.height); assert.strictEqual('srgb', metadata.space);
assert.strictEqual('srgb', metadata.space); assert.strictEqual(3, metadata.channels);
assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
done(); done();
});
}); });
} });
if (sharp.format.gif.input.file) { it('GIF via giflib', function (done) {
it('GIF via giflib', function (done) { sharp(fixtures.inputGif).metadata(function (err, metadata) {
sharp(fixtures.inputGif).metadata(function (err, metadata) { if (err) throw err;
if (err) throw err; assert.strictEqual('gif', metadata.format);
assert.strictEqual('gif', metadata.format); assert.strictEqual(800, metadata.width);
assert.strictEqual(800, metadata.width); assert.strictEqual(533, metadata.height);
assert.strictEqual(533, metadata.height); assert.strictEqual(3, metadata.channels);
assert.strictEqual(3, metadata.channels); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
done(); done();
});
}); });
it('GIF grey+alpha via giflib', function (done) { });
sharp(fixtures.inputGifGreyPlusAlpha).metadata(function (err, metadata) { it('GIF grey+alpha via giflib', function (done) {
if (err) throw err; sharp(fixtures.inputGifGreyPlusAlpha).metadata(function (err, metadata) {
assert.strictEqual('gif', metadata.format); if (err) throw err;
assert.strictEqual(2, metadata.width); assert.strictEqual('gif', metadata.format);
assert.strictEqual(1, metadata.height); assert.strictEqual(2, metadata.width);
assert.strictEqual(2, metadata.channels); assert.strictEqual(1, metadata.height);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(2, metadata.channels);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.orientation);
done(); assert.strictEqual('undefined', typeof metadata.exif);
}); assert.strictEqual('undefined', typeof metadata.icc);
done();
}); });
} });
if (sharp.format.openslide.input.file) {
it('Aperio SVS via openslide', function (done) {
sharp(fixtures.inputSvs).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('openslide', metadata.format);
assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
}
it('File in, Promise out', function (done) { it('File in, Promise out', function (done) {
sharp(fixtures.inputJpg).metadata().then(function (metadata) { sharp(fixtures.inputJpg).metadata().then(function (metadata) {
@@ -194,6 +176,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -222,6 +205,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -244,6 +228,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
@@ -264,6 +249,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);

View File

@@ -155,20 +155,22 @@ describe('Overlays', function () {
}); });
} }
it('Composite JPEG onto PNG', function (done) { it('Composite JPEG onto PNG, no premultiply', function (done) {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpgWithLandscapeExif1) .overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function (error) { .toBuffer(function (err, data, info) {
if (error) return done(error); if (err) throw err;
assert.strictEqual(false, info.premultiplied);
done(); done();
}); });
}); });
it('Composite opaque JPEG onto JPEG', function (done) { it('Composite opaque JPEG onto JPEG, no premultiply', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputJpgWithLandscapeExif1) .overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function (error) { .toBuffer(function (err, data, info) {
if (error) return done(error); if (err) throw err;
assert.strictEqual(false, info.premultiplied);
done(); done();
}); });
}); });
@@ -561,14 +563,15 @@ describe('Overlays', function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(2048, 1536) .resize(2048, 1536)
.overlayWith(data, { raw: info }) .overlayWith(data, { raw: info })
.toBuffer(function (err, data) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.premultiplied);
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done);
}); });
}); });
}); });
it('Throws an error when called with an invalid file', function (done) { it('Returns an error when called with an invalid file', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.overlayWith('notfound.png') .overlayWith('notfound.png')
.toBuffer(function (err) { .toBuffer(function (err) {
@@ -576,4 +579,20 @@ describe('Overlays', function () {
done(); done();
}); });
}); });
it('Composite JPEG onto JPEG, no premultiply', function (done) {
sharp(fixtures.inputJpg)
.resize(480, 320)
.overlayWith(fixtures.inputJpgBooleanTest)
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(480, info.width);
assert.strictEqual(320, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual(false, info.premultiplied);
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-jpeg.jpg'), data, done);
});
});
}); });

View File

@@ -1,15 +0,0 @@
'use strict';
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(bufferutil|sharp)" }] */
describe('Require-time checks', function () {
/**
Including sharp alongside another C++ module that does not require
-stdlib=libc++ (for its C++11 features) has caused clang/llvm to
segfault due to the use of static function variables.
*/
it('Require alongside C++ module that does not use libc++', function () {
const bufferutil = require('bufferutil');
const sharp = require('../../');
});
});

View File

@@ -66,37 +66,47 @@ describe('Resize dimensions', function () {
it('Invalid width - NaN', function () { it('Invalid width - NaN', function () {
assert.throws(function () { assert.throws(function () {
sharp().resize('spoons', 240); sharp().resize('spoons', 240);
}, /Expected integer between 1 and 16383 for width but received spoons of type string/); }, /Expected positive integer for width but received spoons of type string/);
}); });
it('Invalid height - NaN', function () { it('Invalid height - NaN', function () {
assert.throws(function () { assert.throws(function () {
sharp().resize(320, 'spoons'); sharp().resize(320, 'spoons');
}, /Expected integer between 1 and 16383 for height but received spoons of type string/); }, /Expected positive integer for height but received spoons of type string/);
}); });
it('Invalid width - float', function () { it('Invalid width - float', function () {
assert.throws(function () { assert.throws(function () {
sharp().resize(1.5, 240); sharp().resize(1.5, 240);
}, /Expected integer between 1 and 16383 for width but received 1.5 of type number/); }, /Expected positive integer for width but received 1.5 of type number/);
}); });
it('Invalid height - float', function () { it('Invalid height - float', function () {
assert.throws(function () { assert.throws(function () {
sharp().resize(320, 1.5); sharp().resize(320, 1.5);
}, /Expected integer between 1 and 16383 for height but received 1.5 of type number/); }, /Expected positive integer for height but received 1.5 of type number/);
}); });
it('Invalid width - too large', function () { it('Invalid width - too large', function (done) {
assert.throws(function () { sharp(fixtures.inputJpg)
sharp().resize(0x4000, 240); .resize(0x4000, 1)
}, /Expected integer between 1 and 16383 for width but received 16384 of type number/); .webp()
.toBuffer(function (err) {
assert.strictEqual(true, err instanceof Error);
assert.strictEqual('Processed image is too large for the WebP format', err.message);
done();
});
}); });
it('Invalid height - too large', function () { it('Invalid height - too large', function (done) {
assert.throws(function () { sharp(fixtures.inputJpg)
sharp().resize(320, 0x4000); .resize(1, 0x4000)
}, /Expected integer between 1 and 16383 for height but received 16384 of type number/); .webp()
.toBuffer(function (err) {
assert.strictEqual(true, err instanceof Error);
assert.strictEqual('Processed image is too large for the WebP format', err.message);
done();
});
}); });
it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) { it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) {
@@ -402,4 +412,40 @@ describe('Resize dimensions', function () {
sharp().resize(32, 24, { centreSampling: 1 }); sharp().resize(32, 24, { centreSampling: 1 });
}); });
}); });
it('Dimensions that result in differing even shrinks on each axis', function (done) {
sharp(fixtures.inputJpg)
.resize(645, 399)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(645, info.width);
assert.strictEqual(399, info.height);
sharp(data)
.resize(150, 100)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(150, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('resize-diff-shrink-even.jpg'), data, done);
});
});
});
it('Dimensions that result in differing odd shrinks on each axis', function (done) {
return sharp(fixtures.inputJpg)
.resize(600, 399)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(600, info.width);
assert.strictEqual(399, info.height);
sharp(data)
.resize(200)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(133, info.height);
fixtures.assertSimilar(fixtures.expected('resize-diff-shrink-odd.jpg'), data, done);
});
});
});
}); });

View File

@@ -34,6 +34,28 @@ describe('Rotation', function () {
}); });
}); });
[-3690, -450, -90, 90, 450, 3690].forEach(function (angle) {
it('Rotate by any 90-multiple angle (' + angle + 'deg)', function (done) {
sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
});
});
});
[-3780, -540, 0, 180, 540, 3780].forEach(function (angle) {
it('Rotate by any 180-multiple angle (' + angle + 'deg)', function (done) {
sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
});
it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) { it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(240, 240) .resize(240, 240)
@@ -231,4 +253,32 @@ describe('Rotation', function () {
fixtures.assertSimilar(fixtures.inputJpg, data, done); fixtures.assertSimilar(fixtures.inputJpg, data, done);
}); });
}); });
it('Auto-rotate and flip', function (done) {
sharp(fixtures.inputJpgWithExif)
.rotate()
.flip()
.resize(320)
.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('rotate-and-flip.jpg'), data, done);
});
});
it('Auto-rotate and flop', function (done) {
sharp(fixtures.inputJpgWithExif)
.rotate()
.flop()
.resize(320)
.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('rotate-and-flop.jpg'), data, done);
});
});
}); });

View File

@@ -187,7 +187,7 @@ describe('Tile', function () {
assert.strictEqual(2225, info.height); assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
assert.strictEqual('undefined', typeof info.size); assert.strictEqual('undefined', typeof info.size);
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done); assertDeepZoomTiles(directory, 512 + (2 * 16), 13, done);
}); });
}); });
}); });

View File

@@ -20,6 +20,20 @@ describe('Trim borders', function () {
}); });
}); });
it('Skip shrink-on-load', function (done) {
const expected = fixtures.expected('alpha-layer-2-trim-resize.jpg');
sharp(fixtures.inputJpgOverlayLayer2)
.trim()
.resize(300)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(300, info.width);
assert.strictEqual(300, info.height);
fixtures.assertSimilar(expected, data, done);
});
});
it('16-bit PNG with alpha channel', function (done) { it('16-bit PNG with alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency16bit) sharp(fixtures.inputPngWithTransparency16bit)
.resize(32, 32) .resize(32, 32)