mirror of
https://github.com/lovell/sharp.git
synced 2026-02-07 15:16:17 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0feb4156c | ||
|
|
0d1278dade | ||
|
|
1b401b1195 | ||
|
|
11daa3b4d1 | ||
|
|
88a3919ce0 | ||
|
|
c41b87303d | ||
|
|
833aaead56 | ||
|
|
ff98d2e44a | ||
|
|
fcf05f608a | ||
|
|
9baf38db44 | ||
|
|
69050ef1c8 | ||
|
|
b35b9f7850 | ||
|
|
500ae97cac | ||
|
|
d5b7040557 | ||
|
|
ca52894651 | ||
|
|
4009acdd30 | ||
|
|
147c93ecd3 | ||
|
|
8e04e4b07f | ||
|
|
e7413ea1e5 | ||
|
|
220bb03a32 | ||
|
|
20f512fe5f | ||
|
|
efb3523eaa | ||
|
|
2f2276e091 | ||
|
|
08a6597626 | ||
|
|
d82a6ee4fc | ||
|
|
e627f6d68d | ||
|
|
e650f58bd8 | ||
|
|
5a9b6c8afd | ||
|
|
075771d1e9 | ||
|
|
4fcf091fef | ||
|
|
0e66454fe4 |
13
.cirrus.yml
Normal file
13
.cirrus.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
freebsd_instance:
|
||||||
|
image_family: freebsd-12-0
|
||||||
|
|
||||||
|
task:
|
||||||
|
prerequisites_script:
|
||||||
|
- sed -i '' 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf
|
||||||
|
- pkg update -f
|
||||||
|
- pkg upgrade -y
|
||||||
|
- pkg install -y pkgconf vips libnghttp2 node npm
|
||||||
|
install_script:
|
||||||
|
- npm install --unsafe-perm
|
||||||
|
test_script:
|
||||||
|
- npm test
|
||||||
2
.github/ISSUE_TEMPLATE/installation.md
vendored
2
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -11,7 +11,7 @@ Did you see the [documentation relating to installation](https://sharp.pixelplum
|
|||||||
|
|
||||||
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?
|
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 (mis)using `sudo npm install` have you tried with the `sudo npm install --unsafe-perm` flag?
|
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
|
||||||
|
|
||||||
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
|
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
|
||||||
|
|
||||||
|
|||||||
16
.npmignore
16
.npmignore
@@ -1,16 +0,0 @@
|
|||||||
build
|
|
||||||
node_modules
|
|
||||||
coverage
|
|
||||||
.editorconfig
|
|
||||||
.gitattributes
|
|
||||||
.gitignore
|
|
||||||
test
|
|
||||||
.travis.yml
|
|
||||||
appveyor.yml
|
|
||||||
mkdocs.yml
|
|
||||||
docs/css/
|
|
||||||
vendor
|
|
||||||
.prebuildrc
|
|
||||||
.nyc_output
|
|
||||||
.github/
|
|
||||||
.vscode/
|
|
||||||
23
.travis.yml
23
.travis.yml
@@ -18,6 +18,12 @@ matrix:
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: "12"
|
node_js: "12"
|
||||||
|
- name: "Linux (glibc) - Node 13"
|
||||||
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
node_js: "13"
|
||||||
after_success:
|
after_success:
|
||||||
- npm install coveralls
|
- npm install coveralls
|
||||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||||
@@ -51,6 +57,16 @@ matrix:
|
|||||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
script: sudo docker exec sharp sh -c "npm test"
|
||||||
|
- name: "Linux (musl) - Node 13"
|
||||||
|
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:13.0-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: "OS X - Node 8"
|
- name: "OS X - Node 8"
|
||||||
os: osx
|
os: osx
|
||||||
osx_image: xcode9.2
|
osx_image: xcode9.2
|
||||||
@@ -66,3 +82,10 @@ matrix:
|
|||||||
osx_image: xcode9.2
|
osx_image: xcode9.2
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: "12"
|
node_js: "12"
|
||||||
|
- name: "OS X - Node 13"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10
|
||||||
|
language: node_js
|
||||||
|
node_js: "13"
|
||||||
|
cache:
|
||||||
|
npm: false
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ As well as image resizing, operations such as
|
|||||||
rotation, extraction, compositing and gamma correction are available.
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
Most modern 64-bit OS X, Windows and Linux systems running
|
Most modern 64-bit OS X, Windows and Linux systems running
|
||||||
Node versions 8, 10 and 12
|
Node versions 8, 10, 12 and 13
|
||||||
do not require any additional install or runtime dependencies.
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ environment:
|
|||||||
- nodejs_version: "8"
|
- nodejs_version: "8"
|
||||||
- nodejs_version: "10"
|
- nodejs_version: "10"
|
||||||
- nodejs_version: "12"
|
- nodejs_version: "12"
|
||||||
|
- nodejs_version: "13"
|
||||||
install:
|
install:
|
||||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
|
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
|
||||||
- npm install -g npm@6
|
- npm install -g npm@6
|
||||||
|
|||||||
26
binding.gyp
26
binding.gyp
@@ -183,16 +183,22 @@
|
|||||||
},
|
},
|
||||||
'configurations': {
|
'configurations': {
|
||||||
'Release': {
|
'Release': {
|
||||||
'cflags_cc': [
|
'conditions': [
|
||||||
'-Wno-cast-function-type'
|
['OS == "linux"', {
|
||||||
],
|
'cflags_cc': [
|
||||||
'msvs_settings': {
|
'-Wno-cast-function-type'
|
||||||
'VCCLCompilerTool': {
|
]
|
||||||
'ExceptionHandling': 1
|
}],
|
||||||
}
|
['OS == "win"', {
|
||||||
},
|
'msvs_settings': {
|
||||||
'msvs_disabled_warnings': [
|
'VCCLCompilerTool': {
|
||||||
4275
|
'ExceptionHandling': 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'msvs_disabled_warnings': [
|
||||||
|
4275
|
||||||
|
]
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
- `input` **([Buffer][1] \| [String][2])?** if present, can be
|
- `input` **([Buffer][1] \| [String][2])?** if present, can be
|
||||||
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||||
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
||||||
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||||
- `options` **[Object][3]?** if present, is an Object with optional attributes.
|
- `options` **[Object][3]?** if present, is an Object with optional attributes.
|
||||||
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
|
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
|
|||||||
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||||
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||||
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||||
|
- `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
|
||||||
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||||
- `options.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
- `options.skipBlanks` **[Number][9]** 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][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||||
@@ -359,3 +360,5 @@ Returns **Sharp**
|
|||||||
[8]: https://nodejs.org/api/buffer.html
|
[8]: https://nodejs.org/api/buffer.html
|
||||||
|
|
||||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||||
|
|
||||||
|
[10]: https://www.npmjs.org/package/color
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ Returns **Sharp**
|
|||||||
## trim
|
## trim
|
||||||
|
|
||||||
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||||
|
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||||
|
|
||||||
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|||||||
@@ -4,6 +4,32 @@
|
|||||||
|
|
||||||
Requires libvips v8.8.1.
|
Requires libvips v8.8.1.
|
||||||
|
|
||||||
|
#### v0.23.3 - 17<sup>th</sup> November 2019
|
||||||
|
|
||||||
|
* Ensure `trim` operation supports images contained in the alpha channel.
|
||||||
|
[#1597](https://github.com/lovell/sharp/issues/1597)
|
||||||
|
|
||||||
|
* Ensure tile `overlap` option works as expected.
|
||||||
|
[#1921](https://github.com/lovell/sharp/pull/1921)
|
||||||
|
[@rustyguts](https://github.com/rustyguts)
|
||||||
|
|
||||||
|
* Allow compilation on FreeBSD and variants (broken since v0.23.0)
|
||||||
|
[#1952](https://github.com/lovell/sharp/pull/1952)
|
||||||
|
[@pouya-eghbali](https://github.com/pouya-eghbali)
|
||||||
|
|
||||||
|
* Ensure `modulate` and other colour-based operations can co-exist.
|
||||||
|
[#1958](https://github.com/lovell/sharp/issues/1958)
|
||||||
|
|
||||||
|
#### v0.23.2 - 28<sup>th</sup> October 2019
|
||||||
|
|
||||||
|
* Add `background` option to tile output operation.
|
||||||
|
[#1924](https://github.com/lovell/sharp/pull/1924)
|
||||||
|
[@neave](https://github.com/neave)
|
||||||
|
|
||||||
|
* Add support for Node.js 13.
|
||||||
|
[#1932](https://github.com/lovell/sharp/pull/1932)
|
||||||
|
[@MayhemYDG](https://github.com/MayhemYDG)
|
||||||
|
|
||||||
#### v0.23.1 - 26<sup>th</sup> September 2019
|
#### v0.23.1 - 26<sup>th</sup> September 2019
|
||||||
|
|
||||||
* Ensure `sharp.format.vips` is present and correct (filesystem only).
|
* Ensure `sharp.format.vips` is present and correct (filesystem only).
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ As well as image resizing, operations such as
|
|||||||
rotation, extraction, compositing and gamma correction are available.
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
Most modern 64-bit OS X, Windows and Linux systems running
|
Most modern 64-bit OS X, Windows and Linux systems running
|
||||||
Node versions 8, 10 and 12
|
Node versions 8, 10, 12 and 13
|
||||||
do not require any additional install or runtime dependencies.
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||||
@@ -128,6 +128,9 @@ the help and code contributions of the following people:
|
|||||||
* [Jakub Michálek](https://github.com/Goues)
|
* [Jakub Michálek](https://github.com/Goues)
|
||||||
* [Ilya Ovdin](https://github.com/iovdin)
|
* [Ilya Ovdin](https://github.com/iovdin)
|
||||||
* [Andargor](https://github.com/Andargor)
|
* [Andargor](https://github.com/Andargor)
|
||||||
|
* [Nicolas Stepien](https://github.com/MayhemYDG)
|
||||||
|
* [Paul Neave](https://github.com/neave)
|
||||||
|
* [Brendan Kennedy](https://github.com/rustyguts)
|
||||||
|
|
||||||
Thank you!
|
Thank you!
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ yarn add sharp
|
|||||||
### Building from source
|
### Building from source
|
||||||
|
|
||||||
Pre-compiled binaries for sharp are provided for use with
|
Pre-compiled binaries for sharp are provided for use with
|
||||||
Node versions 8, 10 and 12 on
|
Node versions 8, 10, 12 and 13 on
|
||||||
64-bit Windows, OS X and Linux platforms.
|
64-bit Windows, OS X and Linux platforms.
|
||||||
|
|
||||||
Sharp will be built from source at install time when:
|
Sharp will be built from source at install time when:
|
||||||
@@ -74,9 +74,8 @@ libvips is available in the
|
|||||||
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
|
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
apk add vips-dev fftw-dev build-base --update-cache \
|
apk add --upgrade --no-cache vips-dev build-base \
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ \
|
--repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/
|
||||||
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The smaller stack size of musl libc means
|
The smaller stack size of musl libc means
|
||||||
@@ -247,6 +246,9 @@ SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
|
|||||||
|
|
||||||
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
|
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
|
||||||
|
|
||||||
|
To install the prebuilt sharp binaries from a custom URL, please see
|
||||||
|
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
|
||||||
|
|
||||||
### Licences
|
### Licences
|
||||||
|
|
||||||
This module is licensed under the terms of the
|
This module is licensed under the terms of the
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SH
|
|||||||
|
|
||||||
const fail = function (err) {
|
const fail = function (err) {
|
||||||
npmLog.error('sharp', err.message);
|
npmLog.error('sharp', err.message);
|
||||||
|
if (err.code === 'EACCES') {
|
||||||
|
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
|
||||||
|
}
|
||||||
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
|
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
|
||||||
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
|
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -79,14 +82,16 @@ try {
|
|||||||
npmLog.info('sharp', `Downloading ${url}`);
|
npmLog.info('sharp', `Downloading ${url}`);
|
||||||
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
fail(err);
|
||||||
|
} else if (response.statusCode === 404) {
|
||||||
|
fail(new Error(`Prebuilt libvips binaries are not yet available for ${platformAndArch}`));
|
||||||
|
} else if (response.statusCode !== 200) {
|
||||||
|
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
|
||||||
|
} else {
|
||||||
|
response
|
||||||
|
.on('error', fail)
|
||||||
|
.pipe(tmpFile);
|
||||||
}
|
}
|
||||||
if (response.statusCode !== 200) {
|
|
||||||
throw new Error(`Status ${response.statusCode}`);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
.on('error', fail)
|
|
||||||
.pipe(tmpFile);
|
|
||||||
});
|
});
|
||||||
tmpFile
|
tmpFile
|
||||||
.on('error', fail)
|
.on('error', fail)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ const debuglog = util.debuglog('sharp');
|
|||||||
*
|
*
|
||||||
* @param {(Buffer|String)} [input] - if present, can be
|
* @param {(Buffer|String)} [input] - if present, can be
|
||||||
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
||||||
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
||||||
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||||
* @param {Object} [options] - if present, is an Object with optional attributes.
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||||
@@ -213,6 +213,7 @@ const Sharp = function (input, options) {
|
|||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
tileOverlap: 0,
|
tileOverlap: 0,
|
||||||
tileSkipBlanks: -1,
|
tileSkipBlanks: -1,
|
||||||
|
tileBackground: [255, 255, 255, 255],
|
||||||
linearA: 1,
|
linearA: 1,
|
||||||
linearB: 0,
|
linearB: 0,
|
||||||
// Function to notify of libvips warnings
|
// Function to notify of libvips warnings
|
||||||
|
|||||||
@@ -65,11 +65,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
if (is.defined(inputOptions.pages)) {
|
if (is.defined(inputOptions.pages)) {
|
||||||
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
||||||
inputDescriptor.pages = inputOptions.pages;
|
inputDescriptor.pages = inputOptions.pages;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.page)) {
|
if (is.defined(inputOptions.page)) {
|
||||||
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
||||||
inputDescriptor.page = inputOptions.page;
|
inputDescriptor.page = inputOptions.page;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create new image
|
// Create new image
|
||||||
|
|||||||
@@ -551,6 +551,7 @@ function toFormat (format, options) {
|
|||||||
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
* @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.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 {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
|
||||||
|
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
|
||||||
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
* @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 {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.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||||
@@ -574,7 +575,7 @@ function tile (options) {
|
|||||||
if (options.overlap > this.options.tileSize) {
|
if (options.overlap > this.options.tileSize) {
|
||||||
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
|
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
|
||||||
}
|
}
|
||||||
this.options.tileOverlap = tile.overlap;
|
this.options.tileOverlap = options.overlap;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
|
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
|
||||||
}
|
}
|
||||||
@@ -603,6 +604,8 @@ function tile (options) {
|
|||||||
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
|
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Background colour
|
||||||
|
this._setBackgroundColourOption('tileBackground', options.background);
|
||||||
// Depth of tiles
|
// Depth of tiles
|
||||||
if (is.defined(options.depth)) {
|
if (is.defined(options.depth)) {
|
||||||
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
|
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
|
||||||
|
|||||||
@@ -246,9 +246,7 @@ function resize (width, height, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Background
|
// Background
|
||||||
if (is.defined(options.background)) {
|
this._setBackgroundColourOption('resizeBackground', options.background);
|
||||||
this._setBackgroundColourOption('resizeBackground', options.background);
|
|
||||||
}
|
|
||||||
// Kernel
|
// Kernel
|
||||||
if (is.defined(options.kernel)) {
|
if (is.defined(options.kernel)) {
|
||||||
if (is.string(kernel[options.kernel])) {
|
if (is.string(kernel[options.kernel])) {
|
||||||
@@ -369,7 +367,10 @@ function extract (options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||||
|
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||||
|
*
|
||||||
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
|
*
|
||||||
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
|
|||||||
40
package.json
40
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
||||||
"version": "0.23.1",
|
"version": "0.23.3",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://github.com/lovell/sharp",
|
"homepage": "https://github.com/lovell/sharp",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -62,12 +62,14 @@
|
|||||||
"Michael B. Klein <mbklein@gmail.com>",
|
"Michael B. Klein <mbklein@gmail.com>",
|
||||||
"Jordan Prudhomme <jordan@raboland.fr>",
|
"Jordan Prudhomme <jordan@raboland.fr>",
|
||||||
"Ilya Ovdin <iovdin@gmail.com>",
|
"Ilya Ovdin <iovdin@gmail.com>",
|
||||||
"Andargor <andargor@yahoo.com>"
|
"Andargor <andargor@yahoo.com>",
|
||||||
|
"Paul Neave <paul.neave@gmail.com>",
|
||||||
|
"Brendan Kennedy <brenwken@gmail.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||||
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||||
"test": "semistandard && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
|
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci",
|
||||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||||
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||||
"test-coverage": "./test/coverage/report.sh",
|
"test-coverage": "./test/coverage/report.sh",
|
||||||
@@ -75,6 +77,14 @@
|
|||||||
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
|
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
"files": [
|
||||||
|
"binding.gyp",
|
||||||
|
"docs/**",
|
||||||
|
"!docs/css/**",
|
||||||
|
"install/**",
|
||||||
|
"lib/**",
|
||||||
|
"src/**"
|
||||||
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/lovell/sharp"
|
"url": "git://github.com/lovell/sharp"
|
||||||
@@ -100,24 +110,24 @@
|
|||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"nan": "^2.14.0",
|
"nan": "^2.14.0",
|
||||||
"npmlog": "^4.1.2",
|
"npmlog": "^4.1.2",
|
||||||
"prebuild-install": "^5.3.2",
|
"prebuild-install": "^5.3.3",
|
||||||
"semver": "^6.3.0",
|
"semver": "^6.3.0",
|
||||||
"simple-get": "^3.1.0",
|
"simple-get": "^3.1.0",
|
||||||
"tar": "^4.4.13",
|
"tar": "^5.0.5",
|
||||||
"tunnel-agent": "^0.6.0"
|
"tunnel-agent": "^0.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^3.1.0",
|
"async": "^3.1.0",
|
||||||
"cc": "^1.0.2",
|
"cc": "^2.0.1",
|
||||||
"decompress-zip": "^0.3.2",
|
"decompress-zip": "^0.3.2",
|
||||||
"documentation": "^12.1.2",
|
"documentation": "^12.1.4",
|
||||||
"exif-reader": "^1.0.2",
|
"exif-reader": "^1.0.3",
|
||||||
"icc": "^1.0.0",
|
"icc": "^1.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.2",
|
||||||
"mock-fs": "^4.10.1",
|
"mock-fs": "^4.10.3",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^14.1.1",
|
||||||
"prebuild": "^9.1.0",
|
"prebuild": "^9.1.1",
|
||||||
"prebuild-ci": "^3.1.0",
|
"prebuild-ci": "^3.1.0",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
"semistandard": "^14.2.0"
|
"semistandard": "^14.2.0"
|
||||||
@@ -129,6 +139,9 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.5.0"
|
"node": ">=8.5.0"
|
||||||
},
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
"semistandard": {
|
"semistandard": {
|
||||||
"env": [
|
"env": [
|
||||||
"mocha"
|
"mocha"
|
||||||
@@ -137,10 +150,7 @@
|
|||||||
"cc": {
|
"cc": {
|
||||||
"linelength": "120",
|
"linelength": "120",
|
||||||
"filter": [
|
"filter": [
|
||||||
"build/c++11",
|
"build/include"
|
||||||
"build/include",
|
|
||||||
"runtime/indentation_namespace",
|
|
||||||
"runtime/references"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <mutex>
|
#include <mutex> // NOLINT(build/c++11)
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <node_buffer.h>
|
#include <node_buffer.h>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ using vips::VImage;
|
|||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
struct InputDescriptor {
|
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string file;
|
std::string file;
|
||||||
char *buffer;
|
char *buffer;
|
||||||
@@ -92,7 +92,7 @@ namespace sharp {
|
|||||||
|
|
||||||
// Create an InputDescriptor instance from a v8::Object describing an input image
|
// Create an InputDescriptor instance from a v8::Object describing an input image
|
||||||
InputDescriptor* CreateInputDescriptor(
|
InputDescriptor* CreateInputDescriptor(
|
||||||
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist);
|
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
|
||||||
|
|
||||||
enum class ImageType {
|
enum class ImageType {
|
||||||
JPEG,
|
JPEG,
|
||||||
|
|||||||
@@ -191,12 +191,20 @@ namespace sharp {
|
|||||||
VImage alpha = image[image.bands() - 1];
|
VImage alpha = image[image.bands() - 1];
|
||||||
return RemoveAlpha(image)
|
return RemoveAlpha(image)
|
||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)})
|
.linear(
|
||||||
|
{ brightness, saturation, 1},
|
||||||
|
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||||
|
)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
.bandjoin(alpha);
|
.bandjoin(alpha);
|
||||||
} else {
|
} else {
|
||||||
return image
|
return image
|
||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)});
|
.linear(
|
||||||
|
{ brightness, saturation, 1 },
|
||||||
|
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||||
|
)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +266,22 @@ namespace sharp {
|
|||||||
if (HasAlpha(background)) {
|
if (HasAlpha(background)) {
|
||||||
background = background.flatten();
|
background = background.flatten();
|
||||||
}
|
}
|
||||||
int top, width, height;
|
int left, top, width, height;
|
||||||
int const left = image.find_trim(&top, &width, &height, VImage::option()
|
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||||
->set("background", background(0, 0))
|
->set("background", background(0, 0))
|
||||||
->set("threshold", threshold));
|
->set("threshold", threshold));
|
||||||
if (width == 0 || height == 0) {
|
if (width == 0 || height == 0) {
|
||||||
throw VError("Unexpected error while trimming. Try to lower the tolerance");
|
if (HasAlpha(image)) {
|
||||||
|
// Search alpha channel
|
||||||
|
VImage alpha = image[image.bands() - 1];
|
||||||
|
VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
|
||||||
|
left = alpha.find_trim(&top, &width, &height, VImage::option()
|
||||||
|
->set("background", backgroundAlpha(0, 0))
|
||||||
|
->set("threshold", threshold));
|
||||||
|
}
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
throw VError("Unexpected error while trimming. Try to lower the tolerance");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return image.extract_area(left, top, width, height);
|
return image.extract_area(left, top, width, height);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#define STAT64_STRUCT stat
|
#define STAT64_STRUCT stat
|
||||||
#define STAT64_FUNCTION stat
|
#define STAT64_FUNCTION stat
|
||||||
|
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
||||||
|
#define STAT64_STRUCT stat
|
||||||
|
#define STAT64_FUNCTION stat
|
||||||
#else
|
#else
|
||||||
#define STAT64_STRUCT stat64
|
#define STAT64_STRUCT stat64
|
||||||
#define STAT64_FUNCTION stat64
|
#define STAT64_FUNCTION stat64
|
||||||
@@ -959,6 +962,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
};
|
};
|
||||||
suffix = AssembleSuffixString(extname, options);
|
suffix = AssembleSuffixString(extname, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove alpha channel from tile background if image does not contain an alpha channel
|
||||||
|
if (!HasAlpha(image)) {
|
||||||
|
baton->tileBackground.pop_back();
|
||||||
|
}
|
||||||
// Write DZ to file
|
// Write DZ to file
|
||||||
vips::VOption *options = VImage::option()
|
vips::VOption *options = VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
@@ -968,6 +976,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
->set("layout", baton->tileLayout)
|
->set("layout", baton->tileLayout)
|
||||||
->set("suffix", const_cast<char*>(suffix.data()))
|
->set("suffix", const_cast<char*>(suffix.data()))
|
||||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||||
|
->set("background", baton->tileBackground)
|
||||||
->set("skip_blanks", baton->tileSkipBlanks);
|
->set("skip_blanks", baton->tileSkipBlanks);
|
||||||
|
|
||||||
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
||||||
@@ -1374,6 +1383,7 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
||||||
std::string tileContainer = AttrAsStr(options, "tileContainer");
|
std::string tileContainer = AttrAsStr(options, "tileContainer");
|
||||||
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
|
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
|
||||||
|
baton->tileBackground = AttrAsRgba(options, "tileBackground");
|
||||||
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
|
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
|
||||||
if (tileContainer == "zip") {
|
if (tileContainer == "zip") {
|
||||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ struct PipelineBaton {
|
|||||||
VipsForeignDzLayout tileLayout;
|
VipsForeignDzLayout tileLayout;
|
||||||
std::string tileFormat;
|
std::string tileFormat;
|
||||||
int tileAngle;
|
int tileAngle;
|
||||||
|
std::vector<double> tileBackground;
|
||||||
int tileSkipBlanks;
|
int tileSkipBlanks;
|
||||||
VipsForeignDzDepth tileDepth;
|
VipsForeignDzDepth tileDepth;
|
||||||
std::unique_ptr<double[]> recombMatrix;
|
std::unique_ptr<double[]> recombMatrix;
|
||||||
@@ -280,6 +281,7 @@ struct PipelineBaton {
|
|||||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
|
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
|
||||||
tileAngle(0),
|
tileAngle(0),
|
||||||
|
tileBackground{ 255.0, 255.0, 255.0, 255.0 },
|
||||||
tileSkipBlanks(-1),
|
tileSkipBlanks(-1),
|
||||||
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
|
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
test/fixtures/expected/modulate-linear.jpg
vendored
Normal file
BIN
test/fixtures/expected/modulate-linear.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
BIN
test/fixtures/image-in-alpha.png
vendored
Normal file
BIN
test/fixtures/image-in-alpha.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
2
test/fixtures/index.js
vendored
2
test/fixtures/index.js
vendored
@@ -13,6 +13,7 @@ const getPath = function (filename) {
|
|||||||
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
|
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
|
||||||
const fingerprint = function (image, callback) {
|
const fingerprint = function (image, callback) {
|
||||||
sharp(image)
|
sharp(image)
|
||||||
|
.flatten('gray')
|
||||||
.greyscale()
|
.greyscale()
|
||||||
.normalise()
|
.normalise()
|
||||||
.resize(9, 8, { fit: sharp.fit.fill })
|
.resize(9, 8, { fit: sharp.fit.fill })
|
||||||
@@ -87,6 +88,7 @@ module.exports = {
|
|||||||
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
|
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
|
||||||
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
|
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
|
||||||
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
|
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
|
||||||
|
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
|
||||||
|
|
||||||
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
|
|||||||
@@ -648,6 +648,16 @@ describe('Input/output', function () {
|
|||||||
it('Ignore unknown attribute', function () {
|
it('Ignore unknown attribute', function () {
|
||||||
sharp(null, { unknown: true });
|
sharp(null, { unknown: true });
|
||||||
});
|
});
|
||||||
|
it('Invalid page property throws', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(null, { page: -1 });
|
||||||
|
}, /Expected integer between 0 and 100000 for page but received -1 of type number/);
|
||||||
|
});
|
||||||
|
it('Invalid pages property throws', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(null, { pages: '1' });
|
||||||
|
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create new image', function () {
|
describe('create new image', function () {
|
||||||
|
|||||||
@@ -259,4 +259,35 @@ describe('JPEG', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Specifying quantization table provides different JPEG', function (done) {
|
||||||
|
// First generate with default quantization table
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jpeg({ optimiseCoding: false })
|
||||||
|
.toBuffer(function (err, withDefaultQuantizationTable, withInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withDefaultQuantizationTable.length > 0);
|
||||||
|
assert.strictEqual(withDefaultQuantizationTable.length, withInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withInfo.format);
|
||||||
|
assert.strictEqual(320, withInfo.width);
|
||||||
|
assert.strictEqual(240, withInfo.height);
|
||||||
|
// Then generate with different quantization table
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jpeg({ optimiseCoding: false, quantizationTable: 3 })
|
||||||
|
.toBuffer(function (err, withQuantTable3, withoutInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withQuantTable3.length > 0);
|
||||||
|
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withoutInfo.format);
|
||||||
|
assert.strictEqual(320, withoutInfo.width);
|
||||||
|
assert.strictEqual(240, withoutInfo.height);
|
||||||
|
|
||||||
|
// Verify image is same (as mozjpeg may not be present) size or less
|
||||||
|
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantizationTable.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -307,6 +307,20 @@ describe('Image metadata', function () {
|
|||||||
readable.pipe(pipeline);
|
readable.pipe(pipeline);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Stream in, rejected Promise out', () => {
|
||||||
|
const pipeline = sharp();
|
||||||
|
fs
|
||||||
|
.createReadStream(__filename)
|
||||||
|
.pipe(pipeline);
|
||||||
|
|
||||||
|
return pipeline
|
||||||
|
.metadata()
|
||||||
|
.then(
|
||||||
|
() => Promise.reject(new Error('Expected metadata to reject')),
|
||||||
|
err => assert.strictEqual(err.message, 'Input buffer contains unsupported image format')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('Stream', function (done) {
|
it('Stream', function (done) {
|
||||||
const readable = fs.createReadStream(fixtures.inputJpg);
|
const readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
const pipeline = sharp().metadata(function (err, metadata) {
|
const pipeline = sharp().metadata(function (err, metadata) {
|
||||||
@@ -390,6 +404,34 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Include metadata in output, enabled via empty object', () =>
|
||||||
|
sharp(fixtures.inputJpgWithExif)
|
||||||
|
.withMetadata({})
|
||||||
|
.toBuffer()
|
||||||
|
.then((buffer) => sharp(buffer)
|
||||||
|
.metadata()
|
||||||
|
.then(metadata => {
|
||||||
|
assert.strictEqual(true, metadata.hasProfile);
|
||||||
|
assert.strictEqual(8, metadata.orientation);
|
||||||
|
assert.strictEqual('object', typeof metadata.exif);
|
||||||
|
assert.strictEqual(true, metadata.exif instanceof Buffer);
|
||||||
|
// EXIF
|
||||||
|
const exif = exifReader(metadata.exif);
|
||||||
|
assert.strictEqual('object', typeof exif);
|
||||||
|
assert.strictEqual('object', typeof exif.image);
|
||||||
|
assert.strictEqual('number', typeof exif.image.XResolution);
|
||||||
|
// ICC
|
||||||
|
assert.strictEqual('object', typeof metadata.icc);
|
||||||
|
assert.strictEqual(true, metadata.icc instanceof Buffer);
|
||||||
|
const profile = icc.parse(metadata.icc);
|
||||||
|
assert.strictEqual('object', typeof profile);
|
||||||
|
assert.strictEqual('RGB', profile.colorSpace);
|
||||||
|
assert.strictEqual('Perceptual', profile.intent);
|
||||||
|
assert.strictEqual('Monitor', profile.deviceClass);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
it('Remove EXIF metadata after a resize', function (done) {
|
it('Remove EXIF metadata after a resize', function (done) {
|
||||||
sharp(fixtures.inputJpgWithExif)
|
sharp(fixtures.inputJpgWithExif)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
|
|||||||
@@ -122,4 +122,21 @@ describe('Modulate', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to use linear and modulate together', function () {
|
||||||
|
const base = 'modulate-linear.jpg';
|
||||||
|
const actual = fixtures.path('output.' + base);
|
||||||
|
const expected = fixtures.expected(base);
|
||||||
|
|
||||||
|
const contrast = 1.5;
|
||||||
|
const brightness = 0.5;
|
||||||
|
|
||||||
|
return sharp(fixtures.testPattern)
|
||||||
|
.linear(contrast, -(128 * contrast) + 128)
|
||||||
|
.modulate({ brightness })
|
||||||
|
.toFile(actual)
|
||||||
|
.then(function () {
|
||||||
|
fixtures.assertMaxColourDistance(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
const promisify = require('util').promisify;
|
||||||
const rimraf = require('rimraf');
|
const rimraf = require('rimraf');
|
||||||
|
|
||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
@@ -85,6 +86,19 @@ describe('TIFF', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Increasing TIFF quality increases file size', () =>
|
||||||
|
sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||||
|
.tiff({ quality: 40 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(tiff40 => sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||||
|
.tiff({ quality: 90 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(tiff90 =>
|
||||||
|
assert.strictEqual(true, tiff40.length < tiff90.length)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
it('Invalid TIFF quality throws error', function () {
|
it('Invalid TIFF quality throws error', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().tiff({ quality: 101 });
|
sharp().tiff({ quality: 101 });
|
||||||
@@ -137,40 +151,40 @@ describe('TIFF', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('TIFF setting xres and yres on file', function (done) {
|
it('TIFF setting xres and yres on file', () =>
|
||||||
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
|
|
||||||
sharp(fixtures.inputTiff)
|
sharp(fixtures.inputTiff)
|
||||||
.tiff({
|
.tiff({
|
||||||
xres: (res),
|
xres: 1000,
|
||||||
yres: (res)
|
yres: 1000
|
||||||
})
|
})
|
||||||
.toFile(fixtures.outputTiff, (err, info) => {
|
.toFile(fixtures.outputTiff)
|
||||||
if (err) throw err;
|
.then(() => sharp(fixtures.outputTiff)
|
||||||
assert.strictEqual('tiff', info.format);
|
.metadata()
|
||||||
sharp(fixtures.outputTiff).metadata(function (err, metadata) {
|
.then(({ density }) => {
|
||||||
if (err) throw err;
|
assert.strictEqual(true,
|
||||||
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
|
density === 2540 || // libvips <= 8.8.2
|
||||||
rimraf(fixtures.outputTiff, done);
|
density === 25400); // libvips >= 8.8.3
|
||||||
});
|
return promisify(rimraf)(fixtures.outputTiff);
|
||||||
});
|
})
|
||||||
});
|
)
|
||||||
|
);
|
||||||
|
|
||||||
it('TIFF setting xres and yres on buffer', function (done) {
|
it('TIFF setting xres and yres on buffer', () =>
|
||||||
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
|
|
||||||
sharp(fixtures.inputTiff)
|
sharp(fixtures.inputTiff)
|
||||||
.tiff({
|
.tiff({
|
||||||
xres: (res),
|
xres: 1000,
|
||||||
yres: (res)
|
yres: 1000
|
||||||
})
|
})
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer()
|
||||||
if (err) throw err;
|
.then(data => sharp(data)
|
||||||
sharp(data).metadata(function (err, metadata) {
|
.metadata()
|
||||||
if (err) throw err;
|
.then(({ density }) => {
|
||||||
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
|
assert.strictEqual(true,
|
||||||
done();
|
density === 2540 || // libvips <= 8.8.2
|
||||||
});
|
density === 25400); // libvips >= 8.8.3
|
||||||
});
|
})
|
||||||
});
|
)
|
||||||
|
);
|
||||||
|
|
||||||
it('TIFF invalid xres value should throw an error', function () {
|
it('TIFF invalid xres value should throw an error', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
|
|||||||
@@ -91,6 +91,29 @@ const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Verifies tiles at specified level in a given output directory are > size+overlap
|
||||||
|
const assertTileOverlap = function (directory, tileSize, done) {
|
||||||
|
// Get sorted levels
|
||||||
|
const levels = fs.readdirSync(directory).sort((a, b) => a - b);
|
||||||
|
// Select the highest tile level
|
||||||
|
const highestLevel = levels[levels.length - 1];
|
||||||
|
// Get sorted tiles from greatest level
|
||||||
|
const tiles = fs.readdirSync(path.join(directory, highestLevel)).sort();
|
||||||
|
// Select a tile from the approximate center of the image
|
||||||
|
const squareTile = path.join(directory, highestLevel, tiles[Math.floor(tiles.length / 2)]);
|
||||||
|
|
||||||
|
sharp(squareTile).metadata(function (err, metadata) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
// Tile with an overlap should be larger than original size
|
||||||
|
assert.strictEqual(true, metadata.width > tileSize);
|
||||||
|
assert.strictEqual(true, metadata.height > tileSize);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
describe('Tile', function () {
|
describe('Tile', function () {
|
||||||
it('Valid size values pass', function () {
|
it('Valid size values pass', function () {
|
||||||
[1, 8192].forEach(function (size) {
|
[1, 8192].forEach(function (size) {
|
||||||
@@ -297,7 +320,9 @@ describe('Tile', function () {
|
|||||||
assert.strictEqual(2225, info.height);
|
assert.strictEqual(2225, info.height);
|
||||||
assert.strictEqual(3, info.channels);
|
assert.strictEqual(3, info.channels);
|
||||||
assert.strictEqual('undefined', typeof info.size);
|
assert.strictEqual('undefined', typeof info.size);
|
||||||
assertDeepZoomTiles(directory, 512 + (2 * 16), 13, done);
|
assertDeepZoomTiles(directory, 512 + (2 * 16), 13, function () {
|
||||||
|
assertTileOverlap(directory, 512, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,6 +41,21 @@ describe('Trim borders', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('single colour PNG where alpha channel provides the image', () =>
|
||||||
|
sharp(fixtures.inputPngImageInAlpha)
|
||||||
|
.trim()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(916, info.width);
|
||||||
|
assert.strictEqual(137, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
assert.strictEqual(-6, info.trimOffsetLeft);
|
||||||
|
assert.strictEqual(-20, info.trimOffsetTop);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
it('16-bit PNG with alpha channel', function (done) {
|
it('16-bit PNG with alpha channel', function (done) {
|
||||||
sharp(fixtures.inputPngWithTransparency16bit)
|
sharp(fixtures.inputPngWithTransparency16bit)
|
||||||
.resize(32, 32)
|
.resize(32, 32)
|
||||||
|
|||||||
Reference in New Issue
Block a user