Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c610e306df | ||
|
|
417cca6e0d | ||
|
|
2ed4b5ae83 | ||
|
|
16e429cf2c | ||
|
|
6b7ce8a605 | ||
|
|
ba4ce75377 | ||
|
|
76ded7fd28 | ||
|
|
a0d1a7be50 | ||
|
|
690bc43abe | ||
|
|
50b461024d | ||
|
|
6cf0b3240d | ||
|
|
233b015d77 | ||
|
|
28de243c11 | ||
|
|
36e8a3da88 | ||
|
|
119d16cad3 | ||
|
|
38402d3185 | ||
|
|
6c02949fc1 | ||
|
|
b737d4601e | ||
|
|
3ff3353550 | ||
|
|
946d3c81a5 | ||
|
|
628996846d | ||
|
|
631a3597c7 | ||
|
|
cfa4f7d45c |
4
.github/CONTRIBUTING.md
vendored
@@ -44,8 +44,8 @@ Any change that modifies the existing public API should be added to the relevant
|
||||
|
||||
| Release | WIP branch |
|
||||
| ------: | :--------- |
|
||||
| v0.23.0 | vision |
|
||||
| v0.24.0 | wit |
|
||||
| v0.25.0 | yield |
|
||||
|
||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||
|
||||
@@ -98,5 +98,5 @@ Please feel free to ask any questions via a
|
||||
[new issue](https://github.com/lovell/sharp/issues/new).
|
||||
|
||||
If you're unable to post details publicly, please
|
||||
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4)
|
||||
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L5)
|
||||
for private, paid consulting.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -12,3 +12,5 @@ What is the output of running `npm install --verbose sharp`?
|
||||
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
|
||||
|
||||
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime?
|
||||
|
||||
If you're using `sudo npm install` have you tried with the `sudo npm install --unsafe-perm` flag?
|
||||
|
||||
1
.gitignore
vendored
@@ -12,4 +12,5 @@ vendor
|
||||
.gitattributes
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
.vscode/
|
||||
package-lock.json
|
||||
|
||||
@@ -13,3 +13,4 @@ vendor
|
||||
.prebuildrc
|
||||
.nyc_output
|
||||
.github/
|
||||
.vscode/
|
||||
|
||||
32
.travis.yml
@@ -1,11 +1,5 @@
|
||||
matrix:
|
||||
include:
|
||||
- name: "Linux (glibc) - Node 6"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "6"
|
||||
- name: "Linux (glibc) - Node 8"
|
||||
os: linux
|
||||
dist: trusty
|
||||
@@ -27,12 +21,6 @@ matrix:
|
||||
after_success:
|
||||
- npm install coveralls
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
- name: "Linux (glibc) - Node 11"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
- name: "Linux (musl) - Node 8"
|
||||
os: linux
|
||||
dist: trusty
|
||||
@@ -53,16 +41,6 @@ matrix:
|
||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
- name: "Linux (musl) - Node 11"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: true
|
||||
language: minimal
|
||||
before_install:
|
||||
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:11-alpine
|
||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
- name: "Linux (musl) - Node 12"
|
||||
os: linux
|
||||
dist: trusty
|
||||
@@ -73,11 +51,6 @@ matrix:
|
||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
||||
script: sudo docker exec sharp sh -c "npm test"
|
||||
- name: "OS X - Node 6"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "6"
|
||||
- name: "OS X - Node 8"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
@@ -88,11 +61,6 @@ matrix:
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
- name: "OS X - Node 11"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
- name: "OS X - Node 12"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
|
||||
@@ -20,7 +20,7 @@ As well as image resizing, operations such as
|
||||
rotation, extraction, compositing and gamma correction are available.
|
||||
|
||||
Most modern 64-bit OS X, Windows and Linux systems running
|
||||
Node versions 6, 8, 10, 11 and 12
|
||||
Node versions 8, 10 and 12
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -4,10 +4,8 @@ build: off
|
||||
platform: x64
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "8"
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "11"
|
||||
- nodejs_version: "12"
|
||||
install:
|
||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
|
||||
|
||||
14
binding.gyp
@@ -97,7 +97,8 @@
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
'defines': [
|
||||
'_ALLOW_KEYWORD_MACROS'
|
||||
'_ALLOW_KEYWORD_MACROS',
|
||||
'_FILE_OFFSET_BITS=64'
|
||||
],
|
||||
'libraries': [
|
||||
'../vendor/lib/libvips.lib',
|
||||
@@ -183,22 +184,15 @@
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type',
|
||||
'-Wno-deprecated-declarations'
|
||||
'-Wno-cast-function-type'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CPLUSPLUSFLAGS': [
|
||||
'-Wno-deprecated-declarations'
|
||||
]
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
4275,
|
||||
4996
|
||||
4275
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -34,8 +34,9 @@ A `Promise` is returned when `callback` is not provided.
|
||||
- `density`: Number of pixels per inch (DPI), if present
|
||||
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
- `pages`: Number of pages/frames contained within the image, with support for TIFF, PDF, animated GIF and animated WebP
|
||||
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
|
||||
- `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||
- `pagePrimary`: Number of the primary page in a HEIF image
|
||||
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||
- `orientation`: Number value of the EXIF Orientation header, if present
|
||||
|
||||
@@ -89,8 +89,8 @@ This will also convert to and add a web-friendly sRGB ICC profile.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `withMetadata` **[Object][5]?**
|
||||
- `withMetadata.orientation` **[Number][8]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
- `options` **[Object][5]?**
|
||||
- `options.orientation` **[Number][8]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -185,6 +185,8 @@ Use these WebP options for output image.
|
||||
- `options.alphaQuality` **[Number][8]** quality of alpha layer, integer 0-100 (optional, default `100`)
|
||||
- `options.lossless` **[Boolean][6]** use lossless compression mode (optional, default `false`)
|
||||
- `options.nearLossless` **[Boolean][6]** use near_lossless compression mode (optional, default `false`)
|
||||
- `options.smartSubsample` **[Boolean][6]** use high quality chroma subsampling (optional, default `false`)
|
||||
- `options.reductionEffort` **[Number][8]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
|
||||
- `options.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
@@ -232,6 +234,29 @@ sharp('input.svg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## heif
|
||||
|
||||
Use these HEIF options for output image.
|
||||
|
||||
Support for HEIF (HEIC/AVIF) is experimental.
|
||||
Do not use this in production systems.
|
||||
|
||||
Requires a custom, globally-installed libvips compiled with support for libheif.
|
||||
|
||||
Most versions of libheif support only the patent-encumbered HEVC compression format.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?** output options
|
||||
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.compression` **[Boolean][6]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
|
||||
- `options.lossless` **[Boolean][6]** use lossless compression (optional, default `false`)
|
||||
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
@@ -283,13 +308,14 @@ Warning: multiple sharp instances concurrently producing tile output can expose
|
||||
|
||||
### Parameters
|
||||
|
||||
- `tile` **[Object][5]?**
|
||||
- `tile.size` **[Number][8]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||
- `tile.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||
- `tile.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||
- `tile.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
- `tile.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||
- `tile.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
- `options` **[Object][5]?**
|
||||
- `options.size` **[Number][8]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||
- `options.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||
- `options.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||
- `options.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
- `options.skipBlanks` **[Number][8]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
||||
- `options.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||
- `options.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
### v0.23 - "*vision*"
|
||||
|
||||
Requires libvips v8.8.1.
|
||||
|
||||
#### v0.23.0 - 29<sup>th</sup> July 2019
|
||||
|
||||
* Remove `overlayWith` previously deprecated in v0.22.0.
|
||||
|
||||
* Add experimental support for HEIF images. Requires libvips compiled with libheif.
|
||||
[#1105](https://github.com/lovell/sharp/issues/1105)
|
||||
|
||||
* Expose libwebp `smartSubsample` and `reductionEffort` options.
|
||||
[#1545](https://github.com/lovell/sharp/issues/1545)
|
||||
|
||||
* Add experimental support for Worker Threads.
|
||||
[#1558](https://github.com/lovell/sharp/issues/1558)
|
||||
|
||||
* Use libvips' built-in CMYK and sRGB profiles when required.
|
||||
[#1619](https://github.com/lovell/sharp/issues/1619)
|
||||
|
||||
* Drop support for Node.js versions 6 and 11.
|
||||
[#1674](https://github.com/lovell/sharp/issues/1674)
|
||||
|
||||
* Expose `skipBlanks` option for tile-based output.
|
||||
[#1687](https://github.com/lovell/sharp/pull/1687)
|
||||
[@RaboliotTheGrey](https://github.com/RaboliotTheGrey)
|
||||
|
||||
* Allow use of `failOnError` option with Stream-based input.
|
||||
[#1691](https://github.com/lovell/sharp/issues/1691)
|
||||
|
||||
* Fix rotate/extract ordering for non-90 angles.
|
||||
[#1755](https://github.com/lovell/sharp/pull/1755)
|
||||
[@iovdin](https://github.com/iovdin)
|
||||
|
||||
### v0.22 - "*uptake*"
|
||||
|
||||
Requires libvips v8.7.4.
|
||||
|
||||
@@ -16,7 +16,7 @@ As well as image resizing, operations such as
|
||||
rotation, extraction, compositing and gamma correction are available.
|
||||
|
||||
Most modern 64-bit OS X, Windows and Linux systems running
|
||||
Node versions 6, 8, 10, 11 and 12
|
||||
Node versions 8, 10 and 12
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
@@ -125,6 +125,7 @@ the help and code contributions of the following people:
|
||||
* [Keith Belovay](https://github.com/fromkeith)
|
||||
* [Michael B. Klein](https://github.com/mbklein)
|
||||
* [Jakub Michálek](https://github.com/Goues)
|
||||
* [Ilya Ovdin](https://github.com/iovdin)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ yarn add sharp
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Node.js v6+
|
||||
* Node.js v8.5.0+
|
||||
|
||||
### Building from source
|
||||
|
||||
Pre-compiled binaries for sharp are provided for use with
|
||||
Node versions 6, 8, 10, 11 and 12 on
|
||||
Node versions 8, 10 and 12 on
|
||||
64-bit Windows, OS X and Linux platforms.
|
||||
|
||||
Sharp will be built from source at install time when:
|
||||
@@ -36,15 +36,16 @@ Building from source requires:
|
||||
[](https://travis-ci.org/lovell/sharp)
|
||||
|
||||
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 10MB.
|
||||
|
||||
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
|
||||
|
||||
* Debian 7+
|
||||
* Debian 8+
|
||||
* Ubuntu 14.04+
|
||||
* Centos 7+
|
||||
* Alpine 3.8+ (Node 8+)
|
||||
* Fedora
|
||||
* Red Hat Enterprise 7+
|
||||
* CentOS 7+
|
||||
* Alpine 3.10+
|
||||
* Fedora 21+
|
||||
* openSUSE 13.2+
|
||||
* Archlinux
|
||||
* Raspbian Jessie
|
||||
@@ -61,7 +62,8 @@ and `LD_LIBRARY_PATH` at runtime.
|
||||
|
||||
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
|
||||
those based on Red Hat Enterprise 6 (e.g. CentOS 6)
|
||||
compiling libvips from source is recommended.
|
||||
|
||||
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball)
|
||||
@@ -69,11 +71,11 @@ compiling libvips from source is recommended.
|
||||
#### Alpine Linux
|
||||
|
||||
libvips is available in the
|
||||
[testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
|
||||
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
|
||||
|
||||
```sh
|
||||
apk add vips-dev fftw-dev build-base --update-cache \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/edge/testing/ \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
|
||||
```
|
||||
|
||||
@@ -97,7 +99,7 @@ that it can be located using `pkg-config --modversion vips-cpp`.
|
||||
[](https://ci.appveyor.com/project/lovell/sharp)
|
||||
|
||||
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
|
||||
This involves an automated HTTPS download of approximately 14MB.
|
||||
This involves an automated HTTPS download of approximately 10MB.
|
||||
If you are having issues during installation consider removing the directory
|
||||
`C:\Users\[user]\AppData\Roaming\npm-cache\_libvips`.
|
||||
|
||||
@@ -150,7 +152,7 @@ docker pull tailor/docker-libvips
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
Set the Lambda runtime to Node.js 8.10.
|
||||
Set the Lambda runtime to `nodejs10.x`.
|
||||
|
||||
The binaries in the `node_modules` directory of the
|
||||
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html)
|
||||
@@ -160,14 +162,14 @@ On non-Linux machines such as OS X and Windows run the following:
|
||||
|
||||
```sh
|
||||
rm -rf node_modules/sharp
|
||||
npm install --arch=x64 --platform=linux --target=8.10.0 sharp
|
||||
npm install --arch=x64 --platform=linux --target=10.15.0 sharp
|
||||
```
|
||||
|
||||
Alternatively a Docker container closely matching the Lambda runtime can be used:
|
||||
|
||||
```sh
|
||||
rm -rf node_modules/sharp
|
||||
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs8.10 npm install sharp
|
||||
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs10.x npm install sharp
|
||||
```
|
||||
|
||||
To get the best performance select the largest memory available.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const copyFileSync = require('fs-copy-file-sync');
|
||||
const libvips = require('../lib/libvips');
|
||||
const npmLog = require('npmlog');
|
||||
|
||||
@@ -24,7 +23,7 @@ if (process.platform === 'win32') {
|
||||
return /\.dll$/.test(filename);
|
||||
})
|
||||
.forEach(function (filename) {
|
||||
copyFileSync(
|
||||
fs.copyFileSync(
|
||||
path.join(vendorLibDir, filename),
|
||||
path.join(buildReleaseDir, filename)
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ const npmLog = require('npmlog');
|
||||
const semver = require('semver');
|
||||
const simpleGet = require('simple-get');
|
||||
const tar = require('tar');
|
||||
const copyFileSync = require('fs-copy-file-sync');
|
||||
|
||||
const agent = require('../lib/agent');
|
||||
const libvips = require('../lib/libvips');
|
||||
@@ -64,7 +63,7 @@ try {
|
||||
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
|
||||
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) {
|
||||
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.17.0')) {
|
||||
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
// Download to per-process temporary file
|
||||
@@ -97,7 +96,7 @@ try {
|
||||
fs.renameSync(tarPathTemp, tarPathCache);
|
||||
} catch (err) {
|
||||
// Fall back to copy and unlink
|
||||
copyFileSync(tarPathTemp, tarPathCache);
|
||||
fs.copyFileSync(tarPathTemp, tarPathCache);
|
||||
fs.unlinkSync(tarPathTemp);
|
||||
}
|
||||
extractTarball(tarPathCache);
|
||||
|
||||
@@ -72,7 +72,7 @@ function extractChannel (channel) {
|
||||
if (is.integer(channel) && is.inRange(channel, 0, 4)) {
|
||||
this.options.extractChannel = channel;
|
||||
} else {
|
||||
throw new Error('Cannot extract invalid channel ' + channel);
|
||||
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ function bandbool (boolOp) {
|
||||
if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) {
|
||||
this.options.bandBoolOp = boolOp;
|
||||
} else {
|
||||
throw new Error('Invalid bandbool operation ' + boolOp);
|
||||
throw is.invalidParameterError('boolOp', 'one of: and, or, eor', boolOp);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ function grayscale (grayscale) {
|
||||
*/
|
||||
function toColourspace (colourspace) {
|
||||
if (!is.string(colourspace)) {
|
||||
throw new Error('Invalid output colourspace ' + colourspace);
|
||||
throw is.invalidParameterError('colourspace', 'string', colourspace);
|
||||
}
|
||||
this.options.colourspace = colourspace;
|
||||
return this;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const deprecate = require('util').deprecate;
|
||||
|
||||
const is = require('./is');
|
||||
|
||||
/**
|
||||
@@ -154,21 +152,11 @@ function composite (images) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @private
|
||||
*/
|
||||
function overlayWith (input, options) {
|
||||
const blend = (is.object(options) && options.cutout) ? 'dest-in' : 'over';
|
||||
return this.composite([Object.assign({ input, blend }, options)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate the Sharp prototype with composite-related functions.
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
Sharp.prototype.composite = composite;
|
||||
Sharp.prototype.overlayWith = deprecate(overlayWith, 'overlayWith(input, options) is deprecated, use composite([{ input, ...options }]) instead');
|
||||
Sharp.blend = blend;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const stream = require('stream');
|
||||
const events = require('events');
|
||||
@@ -9,6 +8,7 @@ const is = require('./is');
|
||||
require('./libvips').hasVendoredLibvips();
|
||||
|
||||
let sharp;
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
sharp = require('../build/Release/sharp.node');
|
||||
} catch (err) {
|
||||
@@ -110,8 +110,6 @@ const Sharp = function (input, options) {
|
||||
// input options
|
||||
sequentialRead: false,
|
||||
limitInputPixels: Math.pow(0x3FFF, 2),
|
||||
// ICC profiles
|
||||
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
||||
// resize options
|
||||
topOffsetPre: -1,
|
||||
leftOffsetPre: -1,
|
||||
@@ -197,6 +195,8 @@ const Sharp = function (input, options) {
|
||||
webpAlphaQuality: 100,
|
||||
webpLossless: false,
|
||||
webpNearLossless: false,
|
||||
webpSmartSubsample: false,
|
||||
webpReductionEffort: 4,
|
||||
tiffQuality: 80,
|
||||
tiffCompression: 'jpeg',
|
||||
tiffPredictor: 'horizontal',
|
||||
@@ -207,8 +207,12 @@ const Sharp = function (input, options) {
|
||||
tiffTileWidth: 256,
|
||||
tiffXres: 1.0,
|
||||
tiffYres: 1.0,
|
||||
heifQuality: 80,
|
||||
heifLossless: false,
|
||||
heifCompression: 'hevc',
|
||||
tileSize: 256,
|
||||
tileOverlap: 0,
|
||||
tileSkipBlanks: -1,
|
||||
linearA: 1,
|
||||
linearB: 0,
|
||||
// Function to notify of libvips warnings
|
||||
|
||||
BIN
lib/icc/cmyk.icm
BIN
lib/icc/sRGB.icc
59
lib/input.js
@@ -19,7 +19,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
||||
// Plain Object descriptor, e.g. create
|
||||
inputOptions = input;
|
||||
if (is.plainObject(inputOptions.raw)) {
|
||||
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) {
|
||||
// Raw Stream
|
||||
inputDescriptor.buffer = [];
|
||||
}
|
||||
@@ -35,7 +35,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
if (is.bool(inputOptions.failOnError)) {
|
||||
inputDescriptor.failOnError = inputOptions.failOnError;
|
||||
} else {
|
||||
throw new Error('Invalid failOnError (boolean) ' + inputOptions.failOnError);
|
||||
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
|
||||
}
|
||||
}
|
||||
// Density
|
||||
@@ -43,7 +43,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
if (is.inRange(inputOptions.density, 1, 2400)) {
|
||||
inputDescriptor.density = inputOptions.density;
|
||||
} else {
|
||||
throw new Error('Invalid density (1 to 2400) ' + inputOptions.density);
|
||||
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
|
||||
}
|
||||
}
|
||||
// Raw pixel input
|
||||
@@ -115,9 +115,8 @@ function _write (chunk, encoding, callback) {
|
||||
/* istanbul ignore else */
|
||||
if (is.buffer(chunk)) {
|
||||
if (this.options.input.buffer.length === 0) {
|
||||
const that = this;
|
||||
this.on('finish', function () {
|
||||
that.streamInFinished = true;
|
||||
this.on('finish', () => {
|
||||
this.streamInFinished = true;
|
||||
});
|
||||
}
|
||||
this.options.input.buffer.push(chunk);
|
||||
@@ -165,16 +164,15 @@ function _isStreamInput () {
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function clone () {
|
||||
const that = this;
|
||||
// Clone existing options
|
||||
const clone = this.constructor.call();
|
||||
clone.options = Object.assign({}, this.options);
|
||||
// Pass 'finish' event to clone for Stream-based input
|
||||
if (this._isStreamInput()) {
|
||||
this.on('finish', function () {
|
||||
this.on('finish', () => {
|
||||
// Clone inherits input data
|
||||
that._flattenBufferIn();
|
||||
clone.options.bufferIn = that.options.bufferIn;
|
||||
this._flattenBufferIn();
|
||||
clone.options.bufferIn = this.options.bufferIn;
|
||||
clone.emit('finish');
|
||||
});
|
||||
}
|
||||
@@ -195,8 +193,9 @@ function clone () {
|
||||
* - `density`: Number of pixels per inch (DPI), if present
|
||||
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, PDF, animated GIF and animated WebP
|
||||
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
|
||||
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||
* - `pagePrimary`: Number of the primary page in a HEIF image
|
||||
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||
* - `orientation`: Number value of the EXIF Orientation header, if present
|
||||
@@ -223,12 +222,11 @@ function clone () {
|
||||
* @returns {Promise<Object>|Sharp}
|
||||
*/
|
||||
function metadata (callback) {
|
||||
const that = this;
|
||||
if (is.fn(callback)) {
|
||||
if (this._isStreamInput()) {
|
||||
this.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.metadata(that.options, callback);
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.metadata(this.options, callback);
|
||||
});
|
||||
} else {
|
||||
sharp.metadata(this.options, callback);
|
||||
@@ -236,10 +234,10 @@ function metadata (callback) {
|
||||
return this;
|
||||
} else {
|
||||
if (this._isStreamInput()) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
that.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.metadata(that.options, function (err, metadata) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.metadata(this.options, (err, metadata) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -249,8 +247,8 @@ function metadata (callback) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return new Promise(function (resolve, reject) {
|
||||
sharp.metadata(that.options, function (err, metadata) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp.metadata(this.options, (err, metadata) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -292,12 +290,11 @@ function metadata (callback) {
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
function stats (callback) {
|
||||
const that = this;
|
||||
if (is.fn(callback)) {
|
||||
if (this._isStreamInput()) {
|
||||
this.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.stats(that.options, callback);
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.stats(this.options, callback);
|
||||
});
|
||||
} else {
|
||||
sharp.stats(this.options, callback);
|
||||
@@ -305,10 +302,10 @@ function stats (callback) {
|
||||
return this;
|
||||
} else {
|
||||
if (this._isStreamInput()) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
that.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.stats(that.options, function (err, stats) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.on('finish', function () {
|
||||
this._flattenBufferIn();
|
||||
sharp.stats(this.options, (err, stats) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -318,8 +315,8 @@ function stats (callback) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return new Promise(function (resolve, reject) {
|
||||
sharp.stats(that.options, function (err, stats) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp.stats(this.options, (err, stats) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,8 @@ const semver = require('semver');
|
||||
const platform = require('./platform');
|
||||
|
||||
const env = process.env;
|
||||
const minimumLibvipsVersion = env.npm_package_config_libvips || require('../package.json').config.libvips;
|
||||
const minimumLibvipsVersion = env.npm_package_config_libvips || /* istanbul ignore next */
|
||||
require('../package.json').config.libvips;
|
||||
|
||||
const spawnSyncOptions = {
|
||||
encoding: 'utf8',
|
||||
@@ -19,6 +20,7 @@ const mkdirSync = function (dirPath) {
|
||||
try {
|
||||
fs.mkdirSync(dirPath);
|
||||
} catch (err) {
|
||||
/* istanbul ignore if */
|
||||
if (err.code !== 'EEXIST') {
|
||||
throw err;
|
||||
}
|
||||
@@ -26,7 +28,8 @@ const mkdirSync = function (dirPath) {
|
||||
};
|
||||
|
||||
const cachePath = function () {
|
||||
const npmCachePath = env.npm_config_cache || (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
|
||||
const npmCachePath = env.npm_config_cache || /* istanbul ignore next */
|
||||
(env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
|
||||
mkdirSync(npmCachePath);
|
||||
const libvipsCachePath = path.join(npmCachePath, '_libvips');
|
||||
mkdirSync(libvipsCachePath);
|
||||
@@ -51,17 +54,21 @@ const hasVendoredLibvips = function () {
|
||||
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
|
||||
vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
|
||||
} catch (err) {}
|
||||
/* istanbul ignore if */
|
||||
if (vendorVersionId && vendorVersionId !== minimumLibvipsVersion) {
|
||||
throw new Error(`Found vendored libvips v${vendorVersionId} but require v${minimumLibvipsVersion}. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (vendorPlatformId) {
|
||||
/* istanbul ignore else */
|
||||
if (currentPlatformId === vendorPlatformId) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const pkgConfigPath = function () {
|
||||
@@ -81,7 +88,8 @@ const useGlobalLibvips = function () {
|
||||
}
|
||||
|
||||
const globalVipsVersion = globalLibvipsVersion();
|
||||
return !!globalVipsVersion && semver.gte(globalVipsVersion, minimumLibvipsVersion);
|
||||
return !!globalVipsVersion && /* istanbul ignore next */
|
||||
semver.gte(globalVipsVersion, minimumLibvipsVersion);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -55,7 +55,7 @@ function rotate (angle, options) {
|
||||
];
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unsupported angle: must be a number.');
|
||||
throw is.invalidParameterError('angle', 'numeric', angle);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -109,7 +109,7 @@ function sharpen (sigma, flat, jagged) {
|
||||
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
||||
this.options.sharpenFlat = flat;
|
||||
} else {
|
||||
throw new Error('Invalid sharpen level for flat areas (0.0 - 10000.0) ' + flat);
|
||||
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
||||
}
|
||||
}
|
||||
// Control over jagged areas
|
||||
@@ -117,11 +117,11 @@ function sharpen (sigma, flat, jagged) {
|
||||
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
||||
this.options.sharpenJagged = jagged;
|
||||
} else {
|
||||
throw new Error('Invalid sharpen level for jagged areas (0.0 - 10000.0) ' + jagged);
|
||||
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
|
||||
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ function median (size) {
|
||||
// Numeric argument: specific sigma
|
||||
this.options.medianSize = size;
|
||||
} else {
|
||||
throw new Error('Invalid median size ' + size);
|
||||
throw is.invalidParameterError('size', 'integer between 1 and 1000', size);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ function blur (sigma) {
|
||||
// Numeric argument: specific sigma
|
||||
this.options.blurSigma = sigma;
|
||||
} else {
|
||||
throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma);
|
||||
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -205,7 +205,7 @@ function gamma (gamma, gammaOut) {
|
||||
} else if (is.number(gamma) && is.inRange(gamma, 1, 3)) {
|
||||
this.options.gamma = gamma;
|
||||
} else {
|
||||
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
|
||||
throw is.invalidParameterError('gamma', 'number between 1.0 and 3.0', gamma);
|
||||
}
|
||||
if (!is.defined(gammaOut)) {
|
||||
// Default gamma correction for output is same as input
|
||||
@@ -213,7 +213,7 @@ function gamma (gamma, gammaOut) {
|
||||
} else if (is.number(gammaOut) && is.inRange(gammaOut, 1, 3)) {
|
||||
this.options.gammaOut = gammaOut;
|
||||
} else {
|
||||
throw new Error('Invalid output gamma correction (1.0 to 3.0) ' + gammaOut);
|
||||
throw is.invalidParameterError('gammaOut', 'number between 1.0 and 3.0', gammaOut);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -315,7 +315,7 @@ function threshold (threshold, options) {
|
||||
} else if (is.integer(threshold) && is.inRange(threshold, 0, 255)) {
|
||||
this.options.threshold = threshold;
|
||||
} else {
|
||||
throw new Error('Invalid threshold (0 to 255) ' + threshold);
|
||||
throw is.invalidParameterError('threshold', 'integer between 0 and 255', threshold);
|
||||
}
|
||||
if (!is.object(options) || options.greyscale === true || options.grayscale === true) {
|
||||
this.options.thresholdGrayscale = true;
|
||||
@@ -346,7 +346,7 @@ function boolean (operand, operator, options) {
|
||||
if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) {
|
||||
this.options.booleanOp = operator;
|
||||
} else {
|
||||
throw new Error('Invalid boolean operator ' + operator);
|
||||
throw is.invalidParameterError('operator', 'one of: and, or, eor', operator);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -364,17 +364,15 @@ function linear (a, b) {
|
||||
} else if (is.number(a)) {
|
||||
this.options.linearA = a;
|
||||
} else {
|
||||
throw new Error('Invalid linear transform multiplier ' + a);
|
||||
throw is.invalidParameterError('a', 'numeric', a);
|
||||
}
|
||||
|
||||
if (!is.defined(b)) {
|
||||
this.options.linearB = 0.0;
|
||||
} else if (is.number(b)) {
|
||||
this.options.linearB = b;
|
||||
} else {
|
||||
throw new Error('Invalid linear transform offset ' + b);
|
||||
throw is.invalidParameterError('b', 'numeric', b);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -405,7 +403,7 @@ function recomb (inputMatrix) {
|
||||
inputMatrix[2].length !== 3
|
||||
) {
|
||||
// must pass in a kernel
|
||||
throw new Error('Invalid Recomb Matrix');
|
||||
throw new Error('Invalid recombination matrix');
|
||||
}
|
||||
this.options.recombMatrix = [
|
||||
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
|
||||
|
||||
288
lib/output.js
@@ -91,9 +91,7 @@ function toFile (fileOut, callback) {
|
||||
*/
|
||||
function toBuffer (options, callback) {
|
||||
if (is.object(options)) {
|
||||
if (is.bool(options.resolveWithObject)) {
|
||||
this.options.resolveWithObject = options.resolveWithObject;
|
||||
}
|
||||
this._setBooleanOption('resolveWithObject', options.resolveWithObject);
|
||||
}
|
||||
return this._pipeline(is.fn(options) ? options : callback);
|
||||
}
|
||||
@@ -109,19 +107,19 @@ function toBuffer (options, callback) {
|
||||
* .toFile('output-with-metadata.jpg')
|
||||
* .then(info => { ... });
|
||||
*
|
||||
* @param {Object} [withMetadata]
|
||||
* @param {Number} [withMetadata.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withMetadata (withMetadata) {
|
||||
this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true;
|
||||
if (is.object(withMetadata)) {
|
||||
if (is.defined(withMetadata.orientation)) {
|
||||
if (is.integer(withMetadata.orientation) && is.inRange(withMetadata.orientation, 1, 8)) {
|
||||
this.options.withMetadataOrientation = withMetadata.orientation;
|
||||
function withMetadata (options) {
|
||||
this.options.withMetadata = is.bool(options) ? options : true;
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.orientation)) {
|
||||
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
||||
this.options.withMetadataOrientation = options.orientation;
|
||||
} else {
|
||||
throw new Error('Invalid orientation (1 to 8) ' + withMetadata.orientation);
|
||||
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +160,7 @@ function jpeg (options) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.jpegQuality = options.quality;
|
||||
} else {
|
||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.progressive)) {
|
||||
@@ -172,7 +170,7 @@ function jpeg (options) {
|
||||
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||
this.options.jpegChromaSubsampling = options.chromaSubsampling;
|
||||
} else {
|
||||
throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling);
|
||||
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||
}
|
||||
}
|
||||
const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
|
||||
@@ -198,7 +196,7 @@ function jpeg (options) {
|
||||
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
|
||||
this.options.jpegQuantisationTable = quantisationTable;
|
||||
} else {
|
||||
throw new Error('Invalid quantisation table (integer, 0-8) ' + quantisationTable);
|
||||
throw is.invalidParameterError('quantisationTable', 'integer between 0 and 8', quantisationTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +237,7 @@ function png (options) {
|
||||
if (is.integer(options.compressionLevel) && is.inRange(options.compressionLevel, 0, 9)) {
|
||||
this.options.pngCompressionLevel = options.compressionLevel;
|
||||
} else {
|
||||
throw new Error('Invalid compressionLevel (integer, 0-9) ' + options.compressionLevel);
|
||||
throw is.invalidParameterError('compressionLevel', 'integer between 0 and 9', options.compressionLevel);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.adaptiveFiltering)) {
|
||||
@@ -290,6 +288,8 @@ function png (options) {
|
||||
* @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.smartSubsample=false] - use high quality chroma subsampling
|
||||
* @param {Number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
|
||||
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
@@ -299,14 +299,14 @@ function webp (options) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.webpQuality = options.quality;
|
||||
} else {
|
||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.object(options) && is.defined(options.alphaQuality)) {
|
||||
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
|
||||
this.options.webpAlphaQuality = options.alphaQuality;
|
||||
} else {
|
||||
throw new Error('Invalid webp alpha quality (integer, 0-100) ' + options.alphaQuality);
|
||||
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
|
||||
}
|
||||
}
|
||||
if (is.object(options) && is.defined(options.lossless)) {
|
||||
@@ -315,6 +315,16 @@ function webp (options) {
|
||||
if (is.object(options) && is.defined(options.nearLossless)) {
|
||||
this._setBooleanOption('webpNearLossless', options.nearLossless);
|
||||
}
|
||||
if (is.object(options) && is.defined(options.smartSubsample)) {
|
||||
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
|
||||
}
|
||||
if (is.object(options) && is.defined(options.reductionEffort)) {
|
||||
if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
|
||||
this.options.webpReductionEffort = options.reductionEffort;
|
||||
} else {
|
||||
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('webp', options);
|
||||
}
|
||||
|
||||
@@ -352,59 +362,47 @@ function tiff (options) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.tiffQuality = options.quality;
|
||||
} else {
|
||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (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.');
|
||||
}
|
||||
this._setBooleanOption('tiffSquash', options.squash);
|
||||
}
|
||||
// tiling
|
||||
if (is.defined(options.tile)) {
|
||||
if (is.bool(options.tile)) {
|
||||
this.options.tiffTile = options.tile;
|
||||
} else {
|
||||
throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean values allowed for options.tile');
|
||||
}
|
||||
this._setBooleanOption('tiffTile', options.tile);
|
||||
}
|
||||
if (is.defined(options.tileWidth)) {
|
||||
if (is.number(options.tileWidth) && options.tileWidth > 0) {
|
||||
if (is.integer(options.tileWidth) && options.tileWidth > 0) {
|
||||
this.options.tiffTileWidth = options.tileWidth;
|
||||
} else {
|
||||
throw new Error('Invalid Value for tileWidth ' + options.tileWidth + ' Only positive numeric values allowed for options.tileWidth');
|
||||
throw is.invalidParameterError('tileWidth', 'integer greater than zero', options.tileWidth);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.tileHeight)) {
|
||||
if (is.number(options.tileHeight) && options.tileHeight > 0) {
|
||||
if (is.integer(options.tileHeight) && options.tileHeight > 0) {
|
||||
this.options.tiffTileHeight = options.tileHeight;
|
||||
} else {
|
||||
throw new Error('Invalid Value for tileHeight ' + options.tileHeight + ' Only positive numeric values allowed for options.tileHeight');
|
||||
throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
|
||||
}
|
||||
}
|
||||
// pyramid
|
||||
if (is.defined(options.pyramid)) {
|
||||
if (is.bool(options.pyramid)) {
|
||||
this.options.tiffPyramid = options.pyramid;
|
||||
} else {
|
||||
throw new Error('Invalid Value for pyramid ' + options.pyramid + ' Only Boolean values allowed for options.pyramid');
|
||||
}
|
||||
this._setBooleanOption('tiffPyramid', options.pyramid);
|
||||
}
|
||||
// resolution
|
||||
if (is.defined(options.xres)) {
|
||||
if (is.number(options.xres)) {
|
||||
if (is.number(options.xres) && options.xres > 0) {
|
||||
this.options.tiffXres = options.xres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
|
||||
throw is.invalidParameterError('xres', 'number greater than zero', options.xres);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.yres)) {
|
||||
if (is.number(options.yres)) {
|
||||
if (is.number(options.yres) && options.yres > 0) {
|
||||
this.options.tiffYres = options.yres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for yres ' + options.yres + ' Only numeric values allowed for options.yres');
|
||||
throw is.invalidParameterError('yres', 'number greater than zero', options.yres);
|
||||
}
|
||||
}
|
||||
// compression
|
||||
@@ -412,8 +410,7 @@ function tiff (options) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
|
||||
this.options.tiffCompression = options.compression;
|
||||
} else {
|
||||
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, ccittfax4, none`;
|
||||
throw new Error(message);
|
||||
throw is.invalidParameterError('compression', 'one of: lzw, deflate, jpeg, ccittfax4, none', options.compression);
|
||||
}
|
||||
}
|
||||
// predictor
|
||||
@@ -421,14 +418,60 @@ function tiff (options) {
|
||||
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);
|
||||
throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('tiff', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these HEIF options for output image.
|
||||
*
|
||||
* Support for HEIF (HEIC/AVIF) is experimental.
|
||||
* Do not use this in production systems.
|
||||
*
|
||||
* Requires a custom, globally-installed libvips compiled with support for libheif.
|
||||
*
|
||||
* Most versions of libheif support only the patent-encumbered HEVC compression format.
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {Number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {Boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
|
||||
* @param {Boolean} [options.lossless=false] - use lossless compression
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function heif (options) {
|
||||
if (!this.constructor.format.heif.output.buffer) {
|
||||
throw new Error('The heif operation requires libvips to have been installed with support for libheif');
|
||||
}
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.heifQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.lossless)) {
|
||||
if (is.bool(options.lossless)) {
|
||||
this.options.heifLossless = options.lossless;
|
||||
} else {
|
||||
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
|
||||
this.options.heifCompression = options.compression;
|
||||
} else {
|
||||
throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('heif', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force output to be raw, uncompressed uint8 pixel data.
|
||||
*
|
||||
@@ -464,7 +507,7 @@ function toFormat (format, options) {
|
||||
}
|
||||
if (format === 'jpg') format = 'jpeg';
|
||||
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
|
||||
throw new Error('Unsupported output format ' + format);
|
||||
throw is.invalidParameterError('format', 'one of: jpeg, png, webp, tiff, raw', format);
|
||||
}
|
||||
return this[format](options);
|
||||
}
|
||||
@@ -487,79 +530,87 @@ function toFormat (format, options) {
|
||||
* // output_files contains 512x512 tiles grouped by zoom level
|
||||
* });
|
||||
*
|
||||
* @param {Object} [tile]
|
||||
* @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192.
|
||||
* @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
||||
* @param {Number} [tile.angle=0] tile angle of rotation, must be a multiple of 90.
|
||||
* @param {String} [tile.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
* @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||
* @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
||||
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
||||
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
|
||||
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
* @param {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
||||
* @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function tile (tile) {
|
||||
if (is.object(tile)) {
|
||||
function tile (options) {
|
||||
if (is.object(options)) {
|
||||
// Size of square tiles, in pixels
|
||||
if (is.defined(tile.size)) {
|
||||
if (is.integer(tile.size) && is.inRange(tile.size, 1, 8192)) {
|
||||
this.options.tileSize = tile.size;
|
||||
if (is.defined(options.size)) {
|
||||
if (is.integer(options.size) && is.inRange(options.size, 1, 8192)) {
|
||||
this.options.tileSize = options.size;
|
||||
} else {
|
||||
throw new Error('Invalid tile size (1 to 8192) ' + tile.size);
|
||||
throw is.invalidParameterError('size', 'integer between 1 and 8192', options.size);
|
||||
}
|
||||
}
|
||||
// Overlap of tiles, in pixels
|
||||
if (is.defined(tile.overlap)) {
|
||||
if (is.integer(tile.overlap) && is.inRange(tile.overlap, 0, 8192)) {
|
||||
if (tile.overlap > this.options.tileSize) {
|
||||
throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize);
|
||||
if (is.defined(options.overlap)) {
|
||||
if (is.integer(options.overlap) && is.inRange(options.overlap, 0, 8192)) {
|
||||
if (options.overlap > this.options.tileSize) {
|
||||
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
|
||||
}
|
||||
this.options.tileOverlap = tile.overlap;
|
||||
} else {
|
||||
throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
|
||||
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
|
||||
}
|
||||
}
|
||||
// Container
|
||||
if (is.defined(tile.container)) {
|
||||
if (is.string(tile.container) && is.inArray(tile.container, ['fs', 'zip'])) {
|
||||
this.options.tileContainer = tile.container;
|
||||
if (is.defined(options.container)) {
|
||||
if (is.string(options.container) && is.inArray(options.container, ['fs', 'zip'])) {
|
||||
this.options.tileContainer = options.container;
|
||||
} else {
|
||||
throw new Error('Invalid tile container ' + tile.container);
|
||||
throw is.invalidParameterError('container', 'one of: fs, zip', options.container);
|
||||
}
|
||||
}
|
||||
// Layout
|
||||
if (is.defined(tile.layout)) {
|
||||
if (is.string(tile.layout) && is.inArray(tile.layout, ['dz', 'google', 'zoomify'])) {
|
||||
this.options.tileLayout = tile.layout;
|
||||
if (is.defined(options.layout)) {
|
||||
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'zoomify'])) {
|
||||
this.options.tileLayout = options.layout;
|
||||
} else {
|
||||
throw new Error('Invalid tile layout ' + tile.layout);
|
||||
throw is.invalidParameterError('layout', 'one of: dz, google, zoomify', options.layout);
|
||||
}
|
||||
}
|
||||
|
||||
// Angle of rotation,
|
||||
if (is.defined(tile.angle)) {
|
||||
if (is.integer(tile.angle) && !(tile.angle % 90)) {
|
||||
this.options.tileAngle = tile.angle;
|
||||
if (is.defined(options.angle)) {
|
||||
if (is.integer(options.angle) && !(options.angle % 90)) {
|
||||
this.options.tileAngle = options.angle;
|
||||
} else {
|
||||
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + tile.angle);
|
||||
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
|
||||
}
|
||||
}
|
||||
|
||||
// Depth of tiles
|
||||
if (is.defined(tile.depth)) {
|
||||
if (is.string(tile.depth) && is.inArray(tile.depth, ['onepixel', 'onetile', 'one'])) {
|
||||
this.options.tileDepth = tile.depth;
|
||||
if (is.defined(options.depth)) {
|
||||
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
|
||||
this.options.tileDepth = options.depth;
|
||||
} else {
|
||||
throw new Error("Invalid tile depth '" + tile.depth + "', should be one of 'onepixel', 'onetile' or 'one'");
|
||||
throw is.invalidParameterError('depth', 'one of: onepixel, onetile, one', options.depth);
|
||||
}
|
||||
}
|
||||
// Threshold to skip blank tiles
|
||||
if (is.defined(options.skipBlanks)) {
|
||||
if (is.integer(options.skipBlanks) && is.inRange(options.skipBlanks, -1, 65535)) {
|
||||
this.options.tileSkipBlanks = options.skipBlanks;
|
||||
} else {
|
||||
throw is.invalidParameterError('skipBlanks', 'integer between -1 and 255/65535', options.skipBlanks);
|
||||
}
|
||||
} else if (is.defined(options.layout) && options.layout === 'google') {
|
||||
this.options.tileSkipBlanks = 5;
|
||||
}
|
||||
}
|
||||
// Format
|
||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||
this.options.tileFormat = this.options.formatOut;
|
||||
} else if (this.options.formatOut !== 'input') {
|
||||
throw new Error('Invalid tile format ' + this.options.formatOut);
|
||||
throw is.invalidParameterError('format', 'one of: jpeg, png, webp', this.options.formatOut);
|
||||
}
|
||||
|
||||
return this._updateFormatOut('dz');
|
||||
}
|
||||
|
||||
@@ -590,7 +641,7 @@ function _setBooleanOption (key, val) {
|
||||
if (is.bool(val)) {
|
||||
this.options[key] = val;
|
||||
} else {
|
||||
throw new Error('Invalid ' + key + ' (boolean) ' + val);
|
||||
throw is.invalidParameterError(key, 'boolean', val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,6 +650,7 @@ function _setBooleanOption (key, val) {
|
||||
* @private
|
||||
*/
|
||||
function _read () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.options.streamOut) {
|
||||
this.options.streamOut = true;
|
||||
this._pipeline();
|
||||
@@ -611,14 +663,13 @@ function _read () {
|
||||
* @private
|
||||
*/
|
||||
function _pipeline (callback) {
|
||||
const that = this;
|
||||
if (typeof callback === 'function') {
|
||||
// output=file/buffer
|
||||
if (this._isStreamInput()) {
|
||||
// output=file/buffer, input=stream
|
||||
this.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.pipeline(that.options, callback);
|
||||
this.on('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, callback);
|
||||
});
|
||||
} else {
|
||||
// output=file/buffer, input=file/buffer
|
||||
@@ -629,41 +680,31 @@ function _pipeline (callback) {
|
||||
// output=stream
|
||||
if (this._isStreamInput()) {
|
||||
// output=stream, input=stream
|
||||
if (this.streamInFinished) {
|
||||
this.once('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, function (err, data, info) {
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
that.emit('error', err);
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
that.emit('info', info);
|
||||
that.push(data);
|
||||
this.emit('info', info);
|
||||
this.push(data);
|
||||
}
|
||||
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);
|
||||
});
|
||||
this.push(null);
|
||||
});
|
||||
});
|
||||
if (this.streamInFinished) {
|
||||
this.emit('finish');
|
||||
}
|
||||
} else {
|
||||
// output=stream, input=file/buffer
|
||||
sharp.pipeline(this.options, function (err, data, info) {
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
that.emit('error', err);
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
that.emit('info', info);
|
||||
that.push(data);
|
||||
this.emit('info', info);
|
||||
this.push(data);
|
||||
}
|
||||
that.push(null);
|
||||
this.push(null);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
@@ -671,15 +712,15 @@ function _pipeline (callback) {
|
||||
// output=promise
|
||||
if (this._isStreamInput()) {
|
||||
// output=promise, input=stream
|
||||
return new Promise(function (resolve, reject) {
|
||||
that.on('finish', function () {
|
||||
that._flattenBufferIn();
|
||||
sharp.pipeline(that.options, function (err, data, info) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.once('finish', () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (that.options.resolveWithObject) {
|
||||
resolve({ data: data, info: info });
|
||||
if (this.options.resolveWithObject) {
|
||||
resolve({ data, info });
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
@@ -689,12 +730,12 @@ function _pipeline (callback) {
|
||||
});
|
||||
} else {
|
||||
// output=promise, input=file/buffer
|
||||
return new Promise(function (resolve, reject) {
|
||||
sharp.pipeline(that.options, function (err, data, info) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sharp.pipeline(this.options, (err, data, info) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (that.options.resolveWithObject) {
|
||||
if (this.options.resolveWithObject) {
|
||||
resolve({ data: data, info: info });
|
||||
} else {
|
||||
resolve(data);
|
||||
@@ -720,6 +761,7 @@ module.exports = function (Sharp) {
|
||||
png,
|
||||
webp,
|
||||
tiff,
|
||||
heif,
|
||||
raw,
|
||||
toFormat,
|
||||
tile,
|
||||
|
||||
@@ -307,7 +307,7 @@ function extend (extend) {
|
||||
this.options.extendRight = extend.right;
|
||||
this._setColourOption('extendBackground', extend.background);
|
||||
} else {
|
||||
throw new Error('Invalid edge extension ' + extend);
|
||||
throw is.invalidParameterError('extend', 'integer or object', extend);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -349,11 +349,11 @@ function extract (options) {
|
||||
if (is.integer(value) && value >= 0) {
|
||||
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
|
||||
} else {
|
||||
throw new Error('Non-integer value for ' + name + ' of ' + value);
|
||||
throw is.invalidParameterError(name, 'integer', value);
|
||||
}
|
||||
}, this);
|
||||
// Ensure existing rotation occurs before pre-resize extraction
|
||||
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true)) {
|
||||
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true || this.options.rotationAngle !== 0)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
return this;
|
||||
|
||||
33
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
||||
"version": "0.22.1",
|
||||
"version": "0.23.0",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -59,7 +59,9 @@
|
||||
"Daiz <taneli.vatanen@gmail.com>",
|
||||
"Julian Aubourg <j@ubourg.net>",
|
||||
"Keith Belovay <keith@picthrive.com>",
|
||||
"Michael B. Klein <mbklein@gmail.com>"
|
||||
"Michael B. Klein <mbklein@gmail.com>",
|
||||
"Jordan Prudhomme <jordan@raboland.fr>",
|
||||
"Ilya Ovdin <iovdin@gmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||
@@ -93,39 +95,38 @@
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"color": "^3.1.1",
|
||||
"color": "^3.1.2",
|
||||
"detect-libc": "^1.0.3",
|
||||
"fs-copy-file-sync": "^1.1.1",
|
||||
"nan": "^2.13.2",
|
||||
"nan": "^2.14.0",
|
||||
"npmlog": "^4.1.2",
|
||||
"prebuild-install": "^5.3.0",
|
||||
"semver": "^6.0.0",
|
||||
"semver": "^6.3.0",
|
||||
"simple-get": "^3.0.3",
|
||||
"tar": "^4.4.8",
|
||||
"tar": "^4.4.10",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^2.6.2",
|
||||
"async": "^3.1.0",
|
||||
"cc": "^1.0.2",
|
||||
"decompress-zip": "^0.3.2",
|
||||
"documentation": "^10.0.0",
|
||||
"documentation": "^12.0.3",
|
||||
"exif-reader": "^1.0.2",
|
||||
"icc": "^1.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^6.1.4",
|
||||
"mock-fs": "^4.9.0",
|
||||
"nyc": "^14.0.0",
|
||||
"prebuild": "^8.2.1",
|
||||
"prebuild-ci": "^3.0.0",
|
||||
"mocha": "^6.2.0",
|
||||
"mock-fs": "^4.10.1",
|
||||
"nyc": "^14.1.1",
|
||||
"prebuild": "^9.0.1",
|
||||
"prebuild-ci": "^3.1.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"semistandard": "^13.0.1"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.7.4"
|
||||
"libvips": "8.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=8.5.0"
|
||||
},
|
||||
"semistandard": {
|
||||
"env": [
|
||||
|
||||
@@ -110,6 +110,15 @@ namespace sharp {
|
||||
bool IsTiff(std::string const &str) {
|
||||
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
||||
}
|
||||
bool IsHeic(std::string const &str) {
|
||||
return EndsWith(str, ".heic") || EndsWith(str, ".HEIC");
|
||||
}
|
||||
bool IsHeif(std::string const &str) {
|
||||
return EndsWith(str, ".heif") || EndsWith(str, ".HEIF") || IsHeic(str) || IsAvif(str);
|
||||
}
|
||||
bool IsAvif(std::string const &str) {
|
||||
return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
|
||||
}
|
||||
bool IsDz(std::string const &str) {
|
||||
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
||||
}
|
||||
@@ -132,6 +141,7 @@ namespace sharp {
|
||||
case ImageType::TIFF: id = "tiff"; break;
|
||||
case ImageType::GIF: id = "gif"; break;
|
||||
case ImageType::SVG: id = "svg"; break;
|
||||
case ImageType::HEIF: id = "heif"; break;
|
||||
case ImageType::PDF: id = "pdf"; break;
|
||||
case ImageType::MAGICK: id = "magick"; break;
|
||||
case ImageType::OPENSLIDE: id = "openslide"; break;
|
||||
@@ -165,6 +175,8 @@ namespace sharp {
|
||||
imageType = ImageType::GIF;
|
||||
} else if (EndsWith(loader, "SvgBuffer")) {
|
||||
imageType = ImageType::SVG;
|
||||
} else if (EndsWith(loader, "HeifBuffer")) {
|
||||
imageType = ImageType::HEIF;
|
||||
} else if (EndsWith(loader, "PdfBuffer")) {
|
||||
imageType = ImageType::PDF;
|
||||
} else if (EndsWith(loader, "MagickBuffer")) {
|
||||
@@ -196,6 +208,8 @@ namespace sharp {
|
||||
imageType = ImageType::GIF;
|
||||
} else if (EndsWith(loader, "SvgFile")) {
|
||||
imageType = ImageType::SVG;
|
||||
} else if (EndsWith(loader, "HeifFile")) {
|
||||
imageType = ImageType::HEIF;
|
||||
} else if (EndsWith(loader, "PdfFile")) {
|
||||
imageType = ImageType::PDF;
|
||||
} else if (EndsWith(loader, "Ppm")) {
|
||||
@@ -222,6 +236,7 @@ namespace sharp {
|
||||
return
|
||||
imageType == ImageType::GIF ||
|
||||
imageType == ImageType::TIFF ||
|
||||
imageType == ImageType::HEIF ||
|
||||
imageType == ImageType::PDF;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
// Verify platform and compiler compatibility
|
||||
|
||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 7))
|
||||
#error libvips version 8.7.0+ is required - see sharp.pixelplumbing.com/page/install
|
||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 8))
|
||||
#error libvips version 8.8.0+ is required - see sharp.pixelplumbing.com/page/install
|
||||
#endif
|
||||
|
||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||
@@ -101,6 +101,7 @@ namespace sharp {
|
||||
TIFF,
|
||||
GIF,
|
||||
SVG,
|
||||
HEIF,
|
||||
PDF,
|
||||
MAGICK,
|
||||
OPENSLIDE,
|
||||
@@ -123,6 +124,9 @@ namespace sharp {
|
||||
bool IsPng(std::string const &str);
|
||||
bool IsWebp(std::string const &str);
|
||||
bool IsTiff(std::string const &str);
|
||||
bool IsHeic(std::string const &str);
|
||||
bool IsHeif(std::string const &str);
|
||||
bool IsAvif(std::string const &str);
|
||||
bool IsDz(std::string const &str);
|
||||
bool IsDzZip(std::string const &str);
|
||||
bool IsV(std::string const &str);
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
VIPS_NAMESPACE_START
|
||||
|
||||
@@ -563,7 +563,7 @@ VImage::new_from_file( const char *name, VOption *options )
|
||||
}
|
||||
|
||||
VImage
|
||||
VImage::new_from_buffer( void *buf, size_t len, const char *option_string,
|
||||
VImage::new_from_buffer( const void *buf, size_t len, const char *option_string,
|
||||
VOption *options )
|
||||
{
|
||||
const char *operation_name;
|
||||
@@ -588,6 +588,13 @@ VImage::new_from_buffer( void *buf, size_t len, const char *option_string,
|
||||
return( out );
|
||||
}
|
||||
|
||||
VImage
|
||||
VImage::new_from_buffer( const std::string &buf, const char *option_string,
|
||||
VOption *options )
|
||||
{
|
||||
return( new_from_buffer( buf.c_str(), buf.size(), option_string, options ) );
|
||||
}
|
||||
|
||||
VImage
|
||||
VImage::new_matrix( int width, int height )
|
||||
{
|
||||
|
||||
@@ -77,6 +77,9 @@ class MetadataWorker : public Nan::AsyncWorker {
|
||||
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
||||
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
|
||||
}
|
||||
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
||||
baton->pagePrimary = image.get_int("heif-primary");
|
||||
}
|
||||
baton->hasProfile = sharp::HasProfile(image);
|
||||
// Derived attributes
|
||||
baton->hasAlpha = sharp::HasAlpha(image);
|
||||
@@ -158,6 +161,9 @@ class MetadataWorker : public Nan::AsyncWorker {
|
||||
if (baton->pageHeight > 0) {
|
||||
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
|
||||
}
|
||||
if (baton->pagePrimary > -1) {
|
||||
Set(info, New("pagePrimary").ToLocalChecked(), New<v8::Uint32>(baton->pagePrimary));
|
||||
}
|
||||
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
|
||||
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
|
||||
if (baton->orientation > 0) {
|
||||
|
||||
@@ -36,6 +36,7 @@ struct MetadataBaton {
|
||||
int paletteBitDepth;
|
||||
int pages;
|
||||
int pageHeight;
|
||||
int pagePrimary;
|
||||
bool hasProfile;
|
||||
bool hasAlpha;
|
||||
int orientation;
|
||||
@@ -59,6 +60,7 @@ struct MetadataBaton {
|
||||
paletteBitDepth(0),
|
||||
pages(0),
|
||||
pageHeight(0),
|
||||
pagePrimary(-1),
|
||||
hasProfile(false),
|
||||
hasAlpha(false),
|
||||
orientation(0),
|
||||
|
||||
117
src/pipeline.cc
@@ -21,6 +21,8 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
#include <node.h>
|
||||
@@ -30,6 +32,17 @@
|
||||
#include "operations.h"
|
||||
#include "pipeline.h"
|
||||
|
||||
#if defined(WIN32)
|
||||
#define STAT64_STRUCT __stat64
|
||||
#define STAT64_FUNCTION _stat64
|
||||
#elif defined(__APPLE__)
|
||||
#define STAT64_STRUCT stat
|
||||
#define STAT64_FUNCTION stat
|
||||
#else
|
||||
#define STAT64_STRUCT stat64
|
||||
#define STAT64_FUNCTION stat64
|
||||
#endif
|
||||
|
||||
class PipelineWorker : public Nan::AsyncWorker {
|
||||
public:
|
||||
PipelineWorker(
|
||||
@@ -57,16 +70,6 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
// Increment processing task counter
|
||||
g_atomic_int_inc(&sharp::counterProcess);
|
||||
|
||||
std::map<VipsInterpretation, std::string> profileMap;
|
||||
// Default sRGB ICC profile from https://packages.debian.org/sid/all/icc-profiles-free/filelist
|
||||
profileMap.insert(
|
||||
std::pair<VipsInterpretation, std::string>(VIPS_INTERPRETATION_sRGB,
|
||||
baton->iccProfilePath + "sRGB.icc"));
|
||||
// Convert to sRGB using default CMYK profile from http://www.argyllcms.com/cmyk.icm
|
||||
profileMap.insert(
|
||||
std::pair<VipsInterpretation, std::string>(VIPS_INTERPRETATION_CMYK,
|
||||
baton->iccProfilePath + "cmyk.icm"));
|
||||
|
||||
try {
|
||||
// Open input
|
||||
vips::VImage image;
|
||||
@@ -95,9 +98,16 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
}
|
||||
|
||||
// Rotate pre-extract
|
||||
if (baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
sharp::RemoveExifOrientation(image);
|
||||
if (baton->rotateBeforePreExtract) {
|
||||
if (rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
sharp::RemoveExifOrientation(image);
|
||||
}
|
||||
if (baton->rotationAngle != 0.0) {
|
||||
std::vector<double> background;
|
||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||
}
|
||||
}
|
||||
|
||||
// Trim
|
||||
@@ -301,17 +311,15 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
if (sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS) {
|
||||
// Convert to sRGB using embedded profile
|
||||
try {
|
||||
image = image.icc_transform(
|
||||
const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option()
|
||||
image = image.icc_transform("srgb", VImage::option()
|
||||
->set("embedded", TRUE)
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
} catch(...) {
|
||||
// Ignore failure of embedded profile
|
||||
}
|
||||
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
||||
image = image.icc_transform(
|
||||
const_cast<char*>(profileMap[VIPS_INTERPRETATION_sRGB].data()), VImage::option()
|
||||
->set("input_profile", profileMap[VIPS_INTERPRETATION_CMYK].data())
|
||||
image = image.icc_transform("srgb", VImage::option()
|
||||
->set("input_profile", "cmyk")
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
}
|
||||
|
||||
@@ -390,12 +398,13 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("kernel", kernel));
|
||||
}
|
||||
|
||||
// Rotate
|
||||
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
sharp::RemoveExifOrientation(image);
|
||||
// Rotate post-extract 90-angle
|
||||
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
sharp::RemoveExifOrientation(image);
|
||||
}
|
||||
|
||||
|
||||
// Flip (mirror about Y axis)
|
||||
if (baton->flip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
@@ -478,8 +487,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate by degree
|
||||
if (baton->rotationAngle != 0.0) {
|
||||
// Rotate post-extract non-90 angle
|
||||
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
||||
std::vector<double> background;
|
||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||
@@ -680,8 +689,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
// 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()));
|
||||
// Transform colours from embedded profile to output profile
|
||||
if (baton->withMetadata && sharp::HasProfile(image) && profileMap[baton->colourspace] != std::string()) {
|
||||
image = image.icc_transform(const_cast<char*>(profileMap[baton->colourspace].data()),
|
||||
if (baton->withMetadata && sharp::HasProfile(image)) {
|
||||
image = image.icc_transform(vips_enum_nick(VIPS_TYPE_INTERPRETATION, baton->colourspace),
|
||||
VImage::option()->set("embedded", TRUE));
|
||||
}
|
||||
}
|
||||
@@ -747,6 +756,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
->set("smart_subsample", baton->webpSmartSubsample)
|
||||
->set("reduction_effort", baton->webpReductionEffort)
|
||||
->set("alpha_q", baton->webpAlphaQuality)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
@@ -780,6 +791,18 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
vips_area_unref(area);
|
||||
baton->formatOut = "tiff";
|
||||
baton->channels = std::min(baton->channels, 3);
|
||||
} else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == ImageType::HEIF)) {
|
||||
// Write HEIF to buffer
|
||||
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("lossless", baton->heifLossless)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
area->free_fn = nullptr;
|
||||
vips_area_unref(area);
|
||||
baton->formatOut = "heif";
|
||||
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) {
|
||||
// Write raw, uncompressed image data to buffer
|
||||
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
|
||||
@@ -814,6 +837,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
bool const isPng = sharp::IsPng(baton->fileOut);
|
||||
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
||||
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
||||
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
||||
bool const isDz = sharp::IsDz(baton->fileOut);
|
||||
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
||||
bool const isV = sharp::IsV(baton->fileOut);
|
||||
@@ -858,6 +882,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
->set("smart_subsample", baton->webpSmartSubsample)
|
||||
->set("reduction_effort", baton->webpReductionEffort)
|
||||
->set("alpha_q", baton->webpAlphaQuality));
|
||||
baton->formatOut = "webp";
|
||||
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
||||
@@ -880,6 +906,20 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("yres", baton->tiffYres));
|
||||
baton->formatOut = "tiff";
|
||||
baton->channels = std::min(baton->channels, 3);
|
||||
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
||||
(willMatchInput && inputImageType == ImageType::HEIF)) {
|
||||
// Write HEIF to file
|
||||
#ifdef VIPS_TYPE_FOREIGN_HEIF_COMPRESSION
|
||||
if (sharp::IsAvif(baton->fileOut)) {
|
||||
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
|
||||
}
|
||||
#endif
|
||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("lossless", baton->heifLossless));
|
||||
baton->formatOut = "heif";
|
||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||
if (isDzZip) {
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
@@ -898,7 +938,9 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
{"Q", std::to_string(baton->webpQuality)},
|
||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"}
|
||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
||||
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
||||
{"reduction_effort", std::to_string(baton->webpReductionEffort)}
|
||||
};
|
||||
suffix = AssembleSuffixString(".webp", options);
|
||||
} else {
|
||||
@@ -925,7 +967,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("container", baton->tileContainer)
|
||||
->set("layout", baton->tileLayout)
|
||||
->set("suffix", const_cast<char*>(suffix.data()))
|
||||
->set("angle", CalculateAngleRotation(baton->tileAngle));
|
||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||
->set("skip_blanks", baton->tileSkipBlanks);
|
||||
|
||||
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
||||
// not passing anything - libvips will handle choice
|
||||
@@ -1005,8 +1048,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
argv[2] = info;
|
||||
} else {
|
||||
// Add file size to info
|
||||
GStatBuf st;
|
||||
if (g_stat(baton->fileOut.data(), &st) == 0) {
|
||||
struct STAT64_STRUCT st;
|
||||
if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) {
|
||||
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(st.st_size)));
|
||||
}
|
||||
argv[1] = info;
|
||||
@@ -1141,9 +1184,6 @@ NAN_METHOD(pipeline) {
|
||||
|
||||
// Input
|
||||
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist);
|
||||
|
||||
// ICC profile to use when input CMYK image has no embedded profile
|
||||
baton->iccProfilePath = AttrAsStr(options, "iccProfilePath");
|
||||
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ?
|
||||
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
@@ -1304,6 +1344,8 @@ NAN_METHOD(pipeline) {
|
||||
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
|
||||
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
|
||||
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
|
||||
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample");
|
||||
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort");
|
||||
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
|
||||
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
|
||||
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
|
||||
@@ -1319,12 +1361,19 @@ NAN_METHOD(pipeline) {
|
||||
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
||||
AttrAsStr(options, "tiffPredictor").data()));
|
||||
|
||||
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality");
|
||||
baton->heifLossless = AttrTo<bool>(options, "heifLossless");
|
||||
#ifdef VIPS_TYPE_FOREIGN_HEIF_COMPRESSION
|
||||
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||
AttrAsStr(options, "heifCompression").data()));
|
||||
#endif
|
||||
// Tile output
|
||||
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
|
||||
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
||||
std::string tileContainer = AttrAsStr(options, "tileContainer");
|
||||
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
|
||||
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
|
||||
if (tileContainer == "zip") {
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
} else {
|
||||
|
||||
@@ -53,7 +53,6 @@ struct Composite {
|
||||
|
||||
struct PipelineBaton {
|
||||
sharp::InputDescriptor *input;
|
||||
std::string iccProfilePath;
|
||||
int limitInputPixels;
|
||||
std::string formatOut;
|
||||
std::string fileOut;
|
||||
@@ -138,6 +137,8 @@ struct PipelineBaton {
|
||||
int webpAlphaQuality;
|
||||
bool webpNearLossless;
|
||||
bool webpLossless;
|
||||
bool webpSmartSubsample;
|
||||
int webpReductionEffort;
|
||||
int tiffQuality;
|
||||
VipsForeignTiffCompression tiffCompression;
|
||||
VipsForeignTiffPredictor tiffPredictor;
|
||||
@@ -148,6 +149,9 @@ struct PipelineBaton {
|
||||
int tiffTileWidth;
|
||||
double tiffXres;
|
||||
double tiffYres;
|
||||
int heifQuality;
|
||||
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression
|
||||
bool heifLossless;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
int withMetadataOrientation;
|
||||
@@ -169,6 +173,7 @@ struct PipelineBaton {
|
||||
VipsForeignDzLayout tileLayout;
|
||||
std::string tileFormat;
|
||||
int tileAngle;
|
||||
int tileSkipBlanks;
|
||||
VipsForeignDzDepth tileDepth;
|
||||
std::unique_ptr<double[]> recombMatrix;
|
||||
|
||||
@@ -194,7 +199,7 @@ struct PipelineBaton {
|
||||
blurSigma(0.0),
|
||||
brightness(1.0),
|
||||
saturation(1.0),
|
||||
hue(0.0),
|
||||
hue(0),
|
||||
medianSize(0),
|
||||
sharpenSigma(0.0),
|
||||
sharpenFlat(1.0),
|
||||
@@ -237,6 +242,11 @@ struct PipelineBaton {
|
||||
pngColours(256),
|
||||
pngDither(1.0),
|
||||
webpQuality(80),
|
||||
webpAlphaQuality(100),
|
||||
webpNearLossless(false),
|
||||
webpLossless(false),
|
||||
webpSmartSubsample(false),
|
||||
webpReductionEffort(4),
|
||||
tiffQuality(80),
|
||||
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
|
||||
@@ -247,6 +257,9 @@ struct PipelineBaton {
|
||||
tiffTileWidth(256),
|
||||
tiffXres(1.0),
|
||||
tiffYres(1.0),
|
||||
heifQuality(80),
|
||||
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC
|
||||
heifLossless(false),
|
||||
withMetadata(false),
|
||||
withMetadataOrientation(-1),
|
||||
convKernelWidth(0),
|
||||
@@ -265,6 +278,7 @@ struct PipelineBaton {
|
||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
|
||||
tileAngle(0),
|
||||
tileSkipBlanks(-1),
|
||||
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,4 +51,4 @@ NAN_MODULE_INIT(init) {
|
||||
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
|
||||
}
|
||||
|
||||
NODE_MODULE(sharp, init)
|
||||
NAN_MODULE_WORKER_ENABLED(sharp, init)
|
||||
|
||||
@@ -115,7 +115,7 @@ class StatsWorker : public Nan::AsyncWorker {
|
||||
|
||||
std::vector<ChannelStats>::iterator it;
|
||||
int i = 0;
|
||||
for (it=baton->channelStats.begin() ; it < baton->channelStats.end(); it++, i++) {
|
||||
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
|
||||
v8::Local<v8::Object> channelStat = New<v8::Object>();
|
||||
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
|
||||
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
|
||||
@@ -127,7 +127,7 @@ class StatsWorker : public Nan::AsyncWorker {
|
||||
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
|
||||
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
|
||||
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
|
||||
channels->Set(i, channelStat);
|
||||
Set(channels, i, channelStat);
|
||||
}
|
||||
|
||||
Set(info, New("channels").ToLocalChecked(), channels);
|
||||
|
||||
@@ -151,7 +151,7 @@ NAN_METHOD(format) {
|
||||
// Which load/save operations are available for each compressed format?
|
||||
Local<Object> format = New<Object>();
|
||||
for (std::string f : {
|
||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf", "v"
|
||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "heif", "pdf", "v"
|
||||
}) {
|
||||
// Input
|
||||
Local<Boolean> hasInputFile =
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
"test": "node perf && node random && node parallel"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^2.6.1",
|
||||
"async": "^3.1.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"gm": "^1.23.1",
|
||||
"imagemagick": "^0.1.3",
|
||||
"imagemagick-native": "^1.9.3",
|
||||
"jimp": "^0.5.3",
|
||||
"mapnik": "^4.0.1",
|
||||
"semver": "^5.5.1"
|
||||
"jimp": "^0.6.4",
|
||||
"mapnik": "^4.2.1",
|
||||
"semver": "^6.1.2"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=8.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
test/fixtures/alpha-layer-1-fill-low-alpha.png
vendored
|
Before Width: | Height: | Size: 222 KiB |
BIN
test/fixtures/alpha-layer-2-ink-low-alpha.png
vendored
|
Before Width: | Height: | Size: 89 KiB |
BIN
test/fixtures/alpha-layer-2-ink.png
vendored
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 186 KiB |
BIN
test/fixtures/expected/alpha-layer-01-low-alpha.png
vendored
|
Before Width: | Height: | Size: 188 KiB |
BIN
test/fixtures/expected/alpha-layer-01.png
vendored
|
Before Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 208 KiB |
BIN
test/fixtures/expected/alpha-layer-012-low-alpha.png
vendored
|
Before Width: | Height: | Size: 209 KiB |
BIN
test/fixtures/expected/alpha-layer-012.png
vendored
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 179 KiB |
BIN
test/fixtures/expected/alpha-layer-12-low-alpha.png
vendored
|
Before Width: | Height: | Size: 179 KiB |
BIN
test/fixtures/expected/alpha-layer-12.png
vendored
|
Before Width: | Height: | Size: 200 KiB |
BIN
test/fixtures/expected/crop-strategy-attention.jpg
vendored
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
test/fixtures/expected/extend-equal-single.jpg
vendored
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
test/fixtures/expected/extract-rotate-45.jpg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
test/fixtures/expected/rotate-extract-45.jpg
vendored
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
test/fixtures/expected/svg-embedded.png
vendored
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 84 KiB |
3
test/fixtures/index.js
vendored
@@ -80,9 +80,6 @@ module.exports = {
|
||||
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
|
||||
inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
|
||||
inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
|
||||
inputPngOverlayLayer2: getPath('alpha-layer-2-ink.png'),
|
||||
inputPngOverlayLayer1LowAlpha: getPath('alpha-layer-1-fill-low-alpha.png'),
|
||||
inputPngOverlayLayer2LowAlpha: getPath('alpha-layer-2-ink-low-alpha.png'),
|
||||
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
|
||||
inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),
|
||||
inputPngBooleanNoAlpha: getPath('bandbool.png'),
|
||||
|
||||
@@ -16,5 +16,5 @@ for test in ./test/unit/*.js; do
|
||||
--show-leak-kinds=definite,indirect,possible \
|
||||
--num-callers=20 \
|
||||
--trace-children=yes \
|
||||
node node_modules/.bin/mocha --slow=60000 --timeout=120000 --file test/unit/beforeEach.js "$test";
|
||||
node --expose-gc node_modules/.bin/mocha --slow=60000 --timeout=120000 --file test/unit/beforeEach.js "$test";
|
||||
done
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
{
|
||||
cond_libwebp_generic
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libwebp.so.6.0.2
|
||||
obj:*/libwebp.so.*
|
||||
}
|
||||
|
||||
# tiff
|
||||
@@ -188,6 +188,14 @@
|
||||
...
|
||||
fun:FcConfigSubstituteWithPat
|
||||
}
|
||||
{
|
||||
leak_fontconfig_init
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: indirect
|
||||
fun:calloc
|
||||
...
|
||||
fun:FcInitLoadConfigAndFonts
|
||||
}
|
||||
|
||||
# libvips
|
||||
{
|
||||
@@ -220,6 +228,13 @@
|
||||
fun:write_webp.constprop.1
|
||||
fun:vips__webp_write_buffer
|
||||
}
|
||||
{
|
||||
value_libvips_start_thread
|
||||
Memcheck:Value8
|
||||
obj:*/libvips.so.*
|
||||
fun:start_thread
|
||||
fun:clone
|
||||
}
|
||||
{
|
||||
cond_libvips_vips_cast_gen
|
||||
Memcheck:Cond
|
||||
@@ -261,6 +276,14 @@
|
||||
...
|
||||
fun:write_webp_image
|
||||
}
|
||||
{
|
||||
param_libvips_write_buf
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
fun:write
|
||||
...
|
||||
fun:start_thread
|
||||
}
|
||||
{
|
||||
leak_libvips_init
|
||||
Memcheck:Leak
|
||||
@@ -284,6 +307,13 @@
|
||||
...
|
||||
fun:uv__fs_work
|
||||
}
|
||||
{
|
||||
param_libuv_epoll_ctl
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
fun:uv__io_poll
|
||||
}
|
||||
{
|
||||
cond_libuv_work_done
|
||||
Memcheck:Cond
|
||||
@@ -434,6 +464,30 @@
|
||||
...
|
||||
fun:_ZN4node12NodePlatformC1EiPN2v817TracingControllerE
|
||||
}
|
||||
{
|
||||
leak_nodejs_start_isolate_data
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
fun:_Znwm
|
||||
...
|
||||
fun:_ZN4node5StartEPN2v87IsolateEPNS_11IsolateDataERKSt6vectorISsSaISsEES9_
|
||||
}
|
||||
{
|
||||
leak_nodejs_runtime_stackguard_object_isolate
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
fun:_Znwm
|
||||
...
|
||||
fun:_ZN2v88internal18Runtime_StackGuardEiPPNS0_6ObjectEPNS0_7IsolateE
|
||||
}
|
||||
{
|
||||
leak_nodejs_builtin_handleapicall_object_isolate
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
fun:_Znwm
|
||||
...
|
||||
fun:_ZN2v88internal21Builtin_HandleApiCallEiPPNS0_6ObjectEPNS0_7IsolateE
|
||||
}
|
||||
{
|
||||
param_nodejs_delayed_task_scheduler
|
||||
Memcheck:Param
|
||||
|
||||
@@ -12,3 +12,9 @@ beforeEach(function () {
|
||||
sharp.simd(usingSimd);
|
||||
sharp.concurrency(concurrency);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,6 +6,18 @@ const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('Extend', function () {
|
||||
it('extend all sides equally via a single value', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(120)
|
||||
.extend(10)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(140, info.width);
|
||||
assert.strictEqual(118, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extend-equal-single.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('extend all sides equally with RGB', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(120)
|
||||
|
||||
@@ -121,6 +121,32 @@ describe('Partial image extraction', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Extract then rotate non-90 anagle', function (done) {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.extract({ left: 20, top: 10, width: 380, height: 280 })
|
||||
.rotate(45)
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(467, info.width);
|
||||
assert.strictEqual(467, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract-rotate-45.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Rotate then extract non-90 angle', function (done) {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.rotate(45)
|
||||
.extract({ left: 20, top: 10, width: 380, height: 280 })
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(380, info.width);
|
||||
assert.strictEqual(280, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-extract-45.jpg'), data, { threshold: 7 }, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
describe('using the legacy extract(top,left,width,height) syntax', function () {
|
||||
it('String top', function () {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
@@ -72,4 +73,10 @@ describe('failOnError', function () {
|
||||
done(err.message.includes('VipsJpeg: Premature end of JPEG file') ? undefined : err);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles stream-based input', function () {
|
||||
const writable = sharp({ failOnError: false });
|
||||
fs.createReadStream(fixtures.inputJpgTruncated).pipe(writable);
|
||||
return writable.toBuffer();
|
||||
});
|
||||
});
|
||||
|
||||
79
test/unit/heif.js
Normal file
@@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
|
||||
const formatHeifOutputBuffer = sharp.format.heif.output.buffer;
|
||||
|
||||
describe('HEIF (experimental)', () => {
|
||||
describe('Stubbed without support for HEIF', () => {
|
||||
before(() => {
|
||||
sharp.format.heif.output.buffer = false;
|
||||
});
|
||||
after(() => {
|
||||
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
|
||||
});
|
||||
|
||||
it('should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stubbed with support for HEIF', () => {
|
||||
before(() => {
|
||||
sharp.format.heif.output.buffer = true;
|
||||
});
|
||||
after(() => {
|
||||
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
|
||||
});
|
||||
|
||||
it('called without options does not throw an error', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
sharp().heif();
|
||||
});
|
||||
});
|
||||
it('valid quality does not throw an error', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
sharp().heif({ quality: 50 });
|
||||
});
|
||||
});
|
||||
it('invalid quality should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif({ quality: 101 });
|
||||
});
|
||||
});
|
||||
it('non-numeric quality should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif({ quality: 'fail' });
|
||||
});
|
||||
});
|
||||
it('valid lossless does not throw an error', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
sharp().heif({ lossless: true });
|
||||
});
|
||||
});
|
||||
it('non-boolean lossless should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif({ lossless: 'fail' });
|
||||
});
|
||||
});
|
||||
it('valid compression does not throw an error', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
sharp().heif({ compression: 'avc' });
|
||||
});
|
||||
});
|
||||
it('unknown compression should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif({ compression: 'fail' });
|
||||
});
|
||||
});
|
||||
it('invalid compression should throw an error', () => {
|
||||
assert.throws(() => {
|
||||
sharp().heif({ compression: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -392,6 +392,22 @@ describe('Input/output', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Stream input with corrupt header fails gracefully', function (done) {
|
||||
const transformer = sharp();
|
||||
transformer
|
||||
.toBuffer()
|
||||
.then(function () {
|
||||
done(new Error('Unexpectedly resolved Promise'));
|
||||
})
|
||||
.catch(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
fs
|
||||
.createReadStream(fixtures.inputJpgWithCorruptHeader)
|
||||
.pipe(transformer);
|
||||
});
|
||||
|
||||
describe('Output filename with unknown extension', function () {
|
||||
it('Match JPEG input', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
|
||||
@@ -75,8 +75,8 @@ describe('Image metadata', function () {
|
||||
// XMP
|
||||
assert.strictEqual('object', typeof metadata.xmp);
|
||||
assert.strictEqual(true, metadata.xmp instanceof Buffer);
|
||||
assert.strictEqual(12495, metadata.xmp.byteLength);
|
||||
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('http://ns.adobe.com/xap/1.0')), 0);
|
||||
assert.strictEqual(12466, metadata.xmp.byteLength);
|
||||
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,589 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
|
||||
const fixtures = require('../fixtures');
|
||||
const sharp = require('../../');
|
||||
|
||||
// Helpers
|
||||
const getPaths = function (baseName, extension) {
|
||||
if (typeof extension === 'undefined') {
|
||||
extension = 'png';
|
||||
}
|
||||
return {
|
||||
actual: fixtures.path('output.' + baseName + '.' + extension),
|
||||
expected: fixtures.expected(baseName + '.' + extension)
|
||||
};
|
||||
};
|
||||
|
||||
// Test
|
||||
describe('Overlays', function () {
|
||||
it('Overlay transparent PNG file on solid background', function (done) {
|
||||
const paths = getPaths('alpha-layer-01');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay transparent PNG Buffer on solid background', function (done) {
|
||||
const paths = getPaths('alpha-layer-01');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fs.readFileSync(fixtures.inputPngOverlayLayer1))
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay low-alpha transparent PNG on solid background', function (done) {
|
||||
const paths = getPaths('alpha-layer-01-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite three transparent PNGs into one', function (done) {
|
||||
const paths = getPaths('alpha-layer-012');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toBuffer(function (error, data) {
|
||||
if (error) return done(error);
|
||||
sharp(data)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite two transparent PNGs into one', function (done) {
|
||||
const paths = getPaths('alpha-layer-12');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite two low-alpha transparent PNGs into one', function (done) {
|
||||
const paths = getPaths('alpha-layer-12-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2LowAlpha)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite three low-alpha transparent PNGs into one', function (done) {
|
||||
const paths = getPaths('alpha-layer-012-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.toBuffer(function (error, data) {
|
||||
if (error) return done(error);
|
||||
|
||||
sharp(data)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2LowAlpha)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite rgb+alpha PNG onto JPEG', function (done) {
|
||||
const paths = getPaths('overlay-jpeg-with-rgb', 'jpg');
|
||||
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(2048, 1536)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toFile(paths.actual, function (error, info) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 102);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite greyscale+alpha PNG onto JPEG', function (done) {
|
||||
const paths = getPaths('overlay-jpeg-with-greyscale', 'jpg');
|
||||
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400, 300)
|
||||
.overlayWith(fixtures.inputPngWithGreyAlpha)
|
||||
.toFile(paths.actual, function (error, info) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 102);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite WebP onto JPEG', function (done) {
|
||||
const paths = getPaths('overlay-jpeg-with-webp', 'jpg');
|
||||
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(300, 300)
|
||||
.overlayWith(fixtures.inputWebPWithTransparency)
|
||||
.toFile(paths.actual, function (error, info) {
|
||||
if (error) return done(error);
|
||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 102);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite JPEG onto PNG, ensure premultiply', function (done) {
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.premultiplied);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite opaque JPEG onto JPEG, ensure premultiply', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.premultiplied);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Fail when overlay is larger', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toBuffer(function (error) {
|
||||
assert.strictEqual(true, error instanceof Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Fail with empty String parameter', function () {
|
||||
assert.throws(function () {
|
||||
sharp().overlayWith('');
|
||||
});
|
||||
});
|
||||
|
||||
it('Fail with non-String parameter', function () {
|
||||
assert.throws(function () {
|
||||
sharp().overlayWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('Fail with unsupported gravity', function () {
|
||||
assert.throws(function () {
|
||||
sharp()
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1, {
|
||||
gravity: 9
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Empty options', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().overlayWith(fixtures.inputPngOverlayLayer1, {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay with numeric gravity', function () {
|
||||
Object.keys(sharp.gravity).forEach(function (gravity) {
|
||||
it(gravity, function (done) {
|
||||
const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
gravity: gravity
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(65, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay with string-based gravity', function () {
|
||||
Object.keys(sharp.gravity).forEach(function (gravity) {
|
||||
it(gravity, function (done) {
|
||||
const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
gravity: sharp.gravity[gravity]
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(65, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay with tile enabled and gravity', function () {
|
||||
Object.keys(sharp.gravity).forEach(function (gravity) {
|
||||
it(gravity, function (done) {
|
||||
const expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
tile: true,
|
||||
gravity: gravity
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(65, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay with top-left offsets', function () {
|
||||
it('Overlay with 10px top & 10px left offsets', function (done) {
|
||||
const expected = fixtures.expected('overlay-valid-offsets-10-10.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
top: 10,
|
||||
left: 10
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with 100px top & 300px left offsets', function (done) {
|
||||
const expected = fixtures.expected('overlay-valid-offsets-100-300.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
top: 100,
|
||||
left: 300
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with only top offset', function () {
|
||||
assert.throws(function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
top: 1000
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with only left offset', function () {
|
||||
assert.throws(function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
left: 1000
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with negative offsets', function () {
|
||||
assert.throws(function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
top: -1000,
|
||||
left: -1000
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with 0 offset', function (done) {
|
||||
const expected = fixtures.expected('overlay-offset-0.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
top: 0,
|
||||
left: 0
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with offset and gravity', function (done) {
|
||||
const expected = fixtures.expected('overlay-offset-with-gravity.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
left: 10,
|
||||
top: 10,
|
||||
gravity: 4
|
||||
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with offset and gravity and tile', function (done) {
|
||||
const expected = fixtures.expected('overlay-offset-with-gravity-tile.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
left: 10,
|
||||
top: 10,
|
||||
gravity: 4,
|
||||
tile: true
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with offset and tile', function (done) {
|
||||
const expected = fixtures.expected('overlay-offset-with-tile.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
left: 10,
|
||||
top: 10,
|
||||
tile: true
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with invalid tile option', function () {
|
||||
assert.throws(function () {
|
||||
sharp().overlayWith('ignore', { tile: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay with very large offset', function (done) {
|
||||
const expected = fixtures.expected('overlay-very-large-offset.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(400)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
left: 10000,
|
||||
top: 10000
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay 100x100 with 50x50 so bottom edges meet', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(50, 50)
|
||||
.toBuffer(function (err, overlay) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||
.resize(100, 100)
|
||||
.overlayWith(overlay, {
|
||||
top: 50,
|
||||
left: 40
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('overlay-bottom-edges-meet.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('With tile enabled and image rotated 90 degrees', function (done) {
|
||||
const expected = fixtures.expected('overlay-tile-rotated90.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(90)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
tile: true
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(98, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('With tile enabled and image rotated 90 degrees and gravity northwest', function (done) {
|
||||
const expected = fixtures.expected('overlay-tile-rotated90-gravity-northwest.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(90)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
tile: true,
|
||||
gravity: 'northwest'
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(98, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay with cutout enabled and gravity', function () {
|
||||
Object.keys(sharp.gravity).forEach(function (gravity) {
|
||||
it(gravity, function (done) {
|
||||
const expected = fixtures.expected('overlay-cutout-gravity-' + gravity + '.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
cutout: true,
|
||||
gravity: gravity
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(65, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('With cutout enabled and image rotated 90 degrees', function (done) {
|
||||
const expected = fixtures.expected('overlay-cutout-rotated90.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(90)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
cutout: true
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(98, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('With cutout enabled and image rotated 90 degrees and gravity northwest', function (done) {
|
||||
const expected = fixtures.expected('overlay-cutout-rotated90-gravity-northwest.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(90)
|
||||
.resize(80)
|
||||
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||
cutout: true,
|
||||
gravity: 'northwest'
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(98, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite RGBA raw buffer onto JPEG', function (done) {
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.raw()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(2048, 1536)
|
||||
.overlayWith(data, { raw: info })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.premultiplied);
|
||||
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns an error when called with an invalid file', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.overlayWith('notfound.png')
|
||||
.toBuffer(function (err) {
|
||||
assert(err instanceof Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite JPEG onto JPEG', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(480, 320)
|
||||
.overlayWith(fixtures.inputJpgBooleanTest)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(480, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.strictEqual(true, info.premultiplied);
|
||||
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-jpeg.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -338,7 +338,7 @@ describe('Resize fit=cover', function () {
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
assert.strictEqual(-143, info.cropOffsetLeft);
|
||||
assert.strictEqual(-107, info.cropOffsetLeft);
|
||||
assert.strictEqual(0, info.cropOffsetTop);
|
||||
fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done);
|
||||
});
|
||||
|
||||
@@ -394,6 +394,15 @@ describe('Image Stats', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('CMYK input without profile', () =>
|
||||
sharp(fixtures.inputJpgWithCmykNoProfile)
|
||||
.stats()
|
||||
.then(stats => {
|
||||
assert.strictEqual(4, stats.channels.length);
|
||||
assert.strictEqual(true, stats.isOpaque);
|
||||
})
|
||||
);
|
||||
|
||||
it('Stream in, Callback out', function (done) {
|
||||
const readable = fs.createReadStream(fixtures.inputJpg);
|
||||
const pipeline = sharp().stats(function (err, stats) {
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('TIFF', function () {
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size === startSize);
|
||||
assert.strictEqual(startSize, info.size);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -246,6 +246,26 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid skipBlanks threshold values pass', function () {
|
||||
[-1, 0, 255, 65535].forEach(function (skipBlanksThreshold) {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tile({
|
||||
skipBlanks: skipBlanksThreshold
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('InvalidskipBlanks threshold values fail', function () {
|
||||
['zoinks', -2, 65536].forEach(function (skipBlanksThreshold) {
|
||||
assert.throws(function () {
|
||||
sharp().tile({
|
||||
skipBlanks: skipBlanksThreshold
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Deep Zoom layout', function (done) {
|
||||
const directory = fixtures.path('output.dzi_files');
|
||||
rimraf(directory, function () {
|
||||
@@ -364,6 +384,25 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Deep Zoom layout with skipBlanks', function (done) {
|
||||
const directory = fixtures.path('output.256_skip_blanks.dzi_files');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputJpgOverlayLayer2)
|
||||
.tile({
|
||||
size: 256,
|
||||
skipBlanks: 0
|
||||
})
|
||||
.toFile(fixtures.path('output.256_skip_blanks.dzi'), function (err, info) {
|
||||
if (err) throw err;
|
||||
// assert them 0_0.jpeg doesn't exist because it's a white tile
|
||||
const whiteTilePath = path.join(directory, '11', '0_0.jpeg');
|
||||
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
|
||||
// Verify only one depth generated
|
||||
assertDeepZoomTiles(directory, 256, 12, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Zoomify layout', function (done) {
|
||||
const directory = fixtures.path('output.zoomify.dzi');
|
||||
rimraf(directory, function () {
|
||||
@@ -451,6 +490,30 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Zoomify layout with skip blanks', function (done) {
|
||||
const directory = fixtures.path('output.zoomify.skipBlanks.dzi');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputJpgOverlayLayer2)
|
||||
.tile({
|
||||
size: 256,
|
||||
layout: 'zoomify',
|
||||
skipBlanks: 0
|
||||
})
|
||||
.toFile(directory, function (err, info) {
|
||||
if (err) throw err;
|
||||
// assert them 0_0.jpeg doesn't exist because it's a white tile
|
||||
const whiteTilePath = path.join(directory, 'TileGroup0', '2-0-0.jpg');
|
||||
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
|
||||
assert.strictEqual('dz', info.format);
|
||||
assert.strictEqual(2048, info.width);
|
||||
assert.strictEqual(1536, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.strictEqual('number', typeof info.size);
|
||||
assertZoomifyTiles(directory, 256, 4, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout', function (done) {
|
||||
const directory = fixtures.path('output.google.dzi');
|
||||
rimraf(directory, function () {
|
||||
@@ -652,6 +715,31 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout with default skip Blanks', function (done) {
|
||||
const directory = fixtures.path('output.google_depth_skipBlanks.dzi');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputPng)
|
||||
.tile({
|
||||
layout: 'google',
|
||||
size: 256
|
||||
})
|
||||
.toFile(directory, function (err, info) {
|
||||
if (err) throw err;
|
||||
|
||||
const whiteTilePath = path.join(directory, '4', '8', '0.jpg');
|
||||
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
|
||||
|
||||
assert.strictEqual('dz', info.format);
|
||||
assert.strictEqual(2809, info.width);
|
||||
assert.strictEqual(2074, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.strictEqual('number', typeof info.size);
|
||||
|
||||
assertGoogleTiles(directory, 256, 5, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Write to ZIP container using file extension', function (done) {
|
||||
const container = fixtures.path('output.dz.container.zip');
|
||||
const extractTo = fixtures.path('output.dz.container');
|
||||
|
||||
@@ -75,4 +75,54 @@ describe('WebP', function () {
|
||||
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should produce a larger file size using smartSubsample', () =>
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.webp({ smartSubsample: false })
|
||||
.toBuffer()
|
||||
.then(withoutSmartSubsample =>
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.webp({ smartSubsample: true })
|
||||
.toBuffer()
|
||||
.then(withSmartSubsample => {
|
||||
assert.strictEqual(true, withSmartSubsample.length > withoutSmartSubsample.length);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
it('invalid smartSubsample throws', () => {
|
||||
assert.throws(() => {
|
||||
sharp().webp({ smartSubsample: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('should produce a smaller file size with increased reductionEffort', () =>
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.webp()
|
||||
.toBuffer()
|
||||
.then(reductionEffort4 =>
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.webp({ reductionEffort: 6 })
|
||||
.toBuffer()
|
||||
.then(reductionEffort6 => {
|
||||
assert.strictEqual(true, reductionEffort4.length > reductionEffort6.length);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
it('invalid reductionEffort throws', () => {
|
||||
assert.throws(() => {
|
||||
sharp().webp({ reductionEffort: true });
|
||||
});
|
||||
});
|
||||
|
||||
it('out of range reductionEffort throws', () => {
|
||||
assert.throws(() => {
|
||||
sharp().webp({ reductionEffort: -1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||