Compare commits
98 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 | ||
|
|
aa3ce760bb | ||
|
|
ba46ad1fd9 | ||
|
|
11214bab5d | ||
|
|
d87c289b4a | ||
|
|
af45c03b6f | ||
|
|
7d6fadce6b | ||
|
|
6b560f7a85 | ||
|
|
5d4221460d | ||
|
|
d42c383992 | ||
|
|
9c7f6fcb2b | ||
|
|
14af0bda61 | ||
|
|
fb5c393fbd | ||
|
|
69fe21a7ec | ||
|
|
da4e05c118 | ||
|
|
e4333ff6b0 | ||
|
|
4ae8999f62 | ||
|
|
3fa91bb4ce | ||
|
|
23b2e541ab | ||
|
|
5bfcf61a6f | ||
|
|
0778c112a9 | ||
|
|
2c300754a7 | ||
|
|
c610e306df | ||
|
|
417cca6e0d | ||
|
|
2ed4b5ae83 | ||
|
|
16e429cf2c | ||
|
|
6b7ce8a605 | ||
|
|
ba4ce75377 | ||
|
|
76ded7fd28 | ||
|
|
a0d1a7be50 | ||
|
|
690bc43abe | ||
|
|
50b461024d | ||
|
|
6cf0b3240d | ||
|
|
233b015d77 | ||
|
|
28de243c11 | ||
|
|
36e8a3da88 | ||
|
|
119d16cad3 | ||
|
|
38402d3185 | ||
|
|
6c02949fc1 | ||
|
|
b737d4601e | ||
|
|
3ff3353550 | ||
|
|
946d3c81a5 | ||
|
|
628996846d | ||
|
|
631a3597c7 | ||
|
|
cfa4f7d45c | ||
|
|
05d76eeadf | ||
|
|
28a6c53da0 | ||
|
|
6fcd2153c5 | ||
|
|
7ae0512b9b | ||
|
|
0890b59c32 | ||
|
|
df3ce450d9 | ||
|
|
bb0257b318 | ||
|
|
9c3597670d | ||
|
|
aa9b328778 | ||
|
|
159e8dace2 | ||
|
|
3be4d5bb45 | ||
|
|
af7caa7b25 | ||
|
|
b4ede75522 | ||
|
|
9d98114074 | ||
|
|
4ac51899c3 | ||
|
|
90a0382317 | ||
|
|
687795c801 | ||
|
|
2e0fbbb942 | ||
|
|
0a3512d066 | ||
|
|
6032171f91 | ||
|
|
fc178de309 | ||
|
|
3f4398457f | ||
|
|
b494b2e872 |
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
|
||||
23
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -12,9 +12,12 @@ New bugs are assigned a `triage` label whilst under investigation.
|
||||
|
||||
## Submit a new feature request
|
||||
|
||||
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement.
|
||||
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists,
|
||||
it's probably fastest to add a comment to it about your requirement.
|
||||
|
||||
Implementation is usually straightforward if _libvips_ [already supports](https://libvips.github.io/libvips/API/current/) the feature you need.
|
||||
Implementation is usually straightforward if libvips
|
||||
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
|
||||
the feature you need.
|
||||
|
||||
## Submit a Pull Request to fix a bug
|
||||
|
||||
@@ -41,18 +44,18 @@ Any change that modifies the existing public API should be added to the relevant
|
||||
|
||||
| Release | WIP branch |
|
||||
| ------: | :--------- |
|
||||
| v0.22.0 | uptake |
|
||||
| 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>`.
|
||||
|
||||
### Add a new public method
|
||||
|
||||
The API tries to be as fluent as possible. Image processing concepts follow the naming conventions from _libvips_ and, to a lesser extent, _ImageMagick_.
|
||||
The API tries to be as fluent as possible.
|
||||
Image processing concepts follow the naming conventions from libvips and, to a lesser extent, ImageMagick.
|
||||
|
||||
Most methods have optional parameters and assume sensible defaults. Methods with mandatory parameters often have names like `doSomethingWith(X)`.
|
||||
|
||||
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
|
||||
Most methods have optional parameters and assume sensible defaults.
|
||||
Please ensure backwards compatibility where possible.
|
||||
|
||||
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
|
||||
|
||||
@@ -60,7 +63,7 @@ Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to
|
||||
|
||||
A method to be removed should be deprecated in the next major version then removed in the following major version.
|
||||
|
||||
By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0.
|
||||
By way of example, the `background()` method present in v0.20.0 was deprecated in v0.21.0 and removed in v0.22.0.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -95,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.
|
||||
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
open_collective: libvips
|
||||
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
What are you trying to achieve?
|
||||
|
||||
Have you searched for similar feature requests?
|
||||
|
||||
What would you expect the API to look like?
|
||||
|
||||
What alternatives have you considered?
|
||||
|
||||
Is there a sample image that helps explain?
|
||||
18
.github/ISSUE_TEMPLATE/installation.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Installation
|
||||
about: Something went wrong **installing** sharp
|
||||
title: ''
|
||||
labels: installation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/install/)?
|
||||
|
||||
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 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 output of running `npx envinfo --binaries --languages --system --utilities`?
|
||||
20
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Possible bug
|
||||
about: Something unexpected occurred **using** sharp
|
||||
title: ''
|
||||
labels: triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
||||
|
||||
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
|
||||
|
||||
What are the steps to reproduce?
|
||||
|
||||
What is the expected behaviour?
|
||||
|
||||
Are you able to provide a standalone code sample, without other dependencies, that demonstrates this problem?
|
||||
|
||||
Are you able to provide a sample image that helps explain the problem?
|
||||
18
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Question
|
||||
about: For help understanding an existing feature
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
||||
|
||||
What are you trying to achieve?
|
||||
|
||||
Have you searched for similar questions?
|
||||
|
||||
Are you able to provide a standalone code sample that demonstrates this question?
|
||||
|
||||
Are you able to provide a sample image that helps explain the question?
|
||||
2
.gitignore
vendored
@@ -12,4 +12,6 @@ vendor
|
||||
.gitattributes
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
.vscode/
|
||||
package-lock.json
|
||||
.idea
|
||||
|
||||
15
.npmignore
@@ -1,15 +0,0 @@
|
||||
build
|
||||
node_modules
|
||||
coverage
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.gitignore
|
||||
test
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
mkdocs.yml
|
||||
docs/css/
|
||||
vendor
|
||||
.prebuildrc
|
||||
.nyc_output
|
||||
CONTRIBUTING.md
|
||||
52
.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
|
||||
@@ -18,15 +12,21 @@ matrix:
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
after_success:
|
||||
- npm install coveralls
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
- name: "Linux (glibc) - Node 11"
|
||||
- name: "Linux (glibc) - Node 12"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
node_js: "12"
|
||||
- name: "Linux (glibc) - Node 13"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "13"
|
||||
after_success:
|
||||
- npm install coveralls
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
- name: "Linux (musl) - Node 8"
|
||||
os: linux
|
||||
dist: trusty
|
||||
@@ -47,21 +47,26 @@ 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"
|
||||
- name: "Linux (musl) - Node 12"
|
||||
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 run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.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: "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 6"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "6"
|
||||
- name: "OS X - Node 8"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
@@ -72,8 +77,15 @@ matrix:
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
- name: "OS X - Node 11"
|
||||
- name: "OS X - Node 12"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
node_js: "12"
|
||||
- name: "OS X - Node 13"
|
||||
os: osx
|
||||
osx_image: xcode10
|
||||
language: node_js
|
||||
node_js: "13"
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
@@ -11,7 +11,8 @@ is to convert large images in common formats to
|
||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||
|
||||
Resizing an image is typically 4x-5x faster than using the
|
||||
quickest ImageMagick and GraphicsMagick settings.
|
||||
quickest ImageMagick and GraphicsMagick settings
|
||||
due to its use of [libvips](https://github.com/libvips/libvips).
|
||||
|
||||
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||
Lanczos resampling ensures quality is not sacrificed for speed.
|
||||
@@ -20,7 +21,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 and 11
|
||||
Node versions 8, 10, 12 and 13
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
## Examples
|
||||
@@ -96,7 +97,7 @@ Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Licensing
|
||||
|
||||
@@ -4,13 +4,13 @@ build: off
|
||||
platform: x64
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "8"
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "11"
|
||||
- nodejs_version: "12"
|
||||
- nodejs_version: "13"
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm install -g npm@5
|
||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
|
||||
- npm install -g npm@6
|
||||
- npm install
|
||||
test_script:
|
||||
- npm test
|
||||
|
||||
36
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',
|
||||
@@ -182,23 +183,22 @@
|
||||
},
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type',
|
||||
'-Wno-deprecated-declarations'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CPLUSPLUSFLAGS': [
|
||||
'-Wno-deprecated-declarations'
|
||||
]
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
4275,
|
||||
4996
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type'
|
||||
]
|
||||
}],
|
||||
['OS == "win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
4275
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,22 +20,23 @@ and [https://www.cairographics.org/operators/][2]
|
||||
### Parameters
|
||||
|
||||
- `images` **[Array][3]<[Object][4]>** Ordered list of images to composite
|
||||
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data or String containing the path to an image file.
|
||||
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
|
||||
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
|
||||
- `images[].input.create.width` **[Number][7]?**
|
||||
- `images[].input.create.height` **[Number][7]?**
|
||||
- `images[].input.create.channels` **[Number][7]?** 3-4
|
||||
- `images[].input.create.background` **([String][6] \| [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
|
||||
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
|
||||
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
|
||||
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
|
||||
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
|
||||
- `images[].tile` **[Boolean][8]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
|
||||
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
|
||||
- `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
|
||||
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
|
||||
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
|
||||
- `images[].raw.width` **[Number][7]?**
|
||||
- `images[].raw.height` **[Number][7]?**
|
||||
- `images[].raw.channels` **[Number][7]?**
|
||||
- `images[].create` **[Object][4]?** describes a blank overlay to be created.
|
||||
- `images[].create.width` **[Number][7]?**
|
||||
- `images[].create.height` **[Number][7]?**
|
||||
- `images[].create.channels` **[Number][7]?** 3-4
|
||||
- `images[].create.background` **([String][6] \| [Object][4])?** parsed by the [color][9] module to extract values for red, green, blue and alpha.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -74,8 +75,8 @@ Returns **Sharp**
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[8]: https://www.npmjs.org/package/color
|
||||
|
||||
[9]: https://www.npmjs.org/package/color
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
- `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 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.
|
||||
- `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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -287,6 +287,41 @@ sharp(input)
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## modulate
|
||||
|
||||
Transforms the image using brightness, saturation and hue rotation.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][2]?**
|
||||
- `options.brightness` **[Number][1]?** Brightness multiplier
|
||||
- `options.saturation` **[Number][1]?** Saturation multiplier
|
||||
- `options.hue` **[Number][1]?** Degrees for hue rotation
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.modulate({
|
||||
brightness: 2 // increase lightness by a factor of 2
|
||||
});
|
||||
|
||||
sharp(input)
|
||||
.modulate({
|
||||
hue: 180 // hue-rotate by 180 degrees
|
||||
});
|
||||
|
||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
sharp(input)
|
||||
.modulate({
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
hue: 90
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
@@ -8,12 +8,15 @@ If an explicit output format is not selected, it will be inferred from the exten
|
||||
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
|
||||
Note that raw pixel data is only supported for buffer output.
|
||||
|
||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
See [withMetadata][1] for control over this.
|
||||
|
||||
A `Promise` is returned when `callback` is not provided.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `fileOut` **[String][1]** the path to write the image data to.
|
||||
- `callback` **[Function][2]?** called on completion with two arguments `(err, info)`.
|
||||
- `fileOut` **[String][2]** the path to write the image data to.
|
||||
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
|
||||
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
@@ -32,15 +35,19 @@ sharp(input)
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid parameters
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **[Promise][4]<[Object][5]>** when no callback is provided
|
||||
Returns **[Promise][5]<[Object][6]>** when no callback is provided
|
||||
|
||||
## toBuffer
|
||||
|
||||
Write output to a Buffer.
|
||||
JPEG, PNG, WebP, TIFF and RAW output are supported.
|
||||
By default, the format will match the input image, except GIF and SVG input which become PNG output.
|
||||
|
||||
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||
|
||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
See [withMetadata][1] for control over this.
|
||||
|
||||
`callback`, if present, gets three arguments `(err, data, info)` where:
|
||||
|
||||
@@ -54,9 +61,9 @@ A `Promise` is returned when `callback` is not provided.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?**
|
||||
- `options.resolveWithObject` **[Boolean][6]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
- `callback` **[Function][2]?**
|
||||
- `options` **[Object][6]?**
|
||||
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
- `callback` **[Function][3]?**
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -79,7 +86,7 @@ sharp(input)
|
||||
.catch(err => { ... });
|
||||
```
|
||||
|
||||
Returns **[Promise][4]<[Buffer][7]>** when no callback is provided
|
||||
Returns **[Promise][5]<[Buffer][8]>** when no callback is provided
|
||||
|
||||
## withMetadata
|
||||
|
||||
@@ -89,8 +96,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][6]?**
|
||||
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -101,7 +108,7 @@ sharp('input.jpg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid parameters
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -111,19 +118,19 @@ Use these JPEG options for output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?** output options
|
||||
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.chromaSubsampling` **[String][1]** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`)
|
||||
- `options.trellisQuantisation` **[Boolean][6]** apply trellis quantisation, requires mozjpeg (optional, default `false`)
|
||||
- `options.overshootDeringing` **[Boolean][6]** apply overshoot deringing, requires mozjpeg (optional, default `false`)
|
||||
- `options.optimiseScans` **[Boolean][6]** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
|
||||
- `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`)
|
||||
- `options.optimiseCoding` **[Boolean][6]** optimise Huffman coding tables (optional, default `true`)
|
||||
- `options.optimizeCoding` **[Boolean][6]** alternative spelling of optimiseCoding (optional, default `true`)
|
||||
- `options.quantisationTable` **[Number][8]** quantization table to use, integer 0-8, requires mozjpeg (optional, default `0`)
|
||||
- `options.quantizationTable` **[Number][8]** alternative spelling of quantisationTable (optional, default `0`)
|
||||
- `options.force` **[Boolean][6]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`)
|
||||
- `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
|
||||
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
|
||||
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`)
|
||||
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
|
||||
- `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
|
||||
- `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`)
|
||||
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -137,7 +144,7 @@ const data = await sharp(input)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -150,16 +157,16 @@ Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?**
|
||||
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.compressionLevel` **[Number][8]** zlib compression level, 0-9 (optional, default `9`)
|
||||
- `options.adaptiveFiltering` **[Boolean][6]** use adaptive row filtering (optional, default `false`)
|
||||
- `options.palette` **[Boolean][6]** quantise to a palette-based image with alpha transparency support, requires libimagequant (optional, default `false`)
|
||||
- `options.quality` **[Number][8]** use the lowest number of colours needed to achieve given quality, requires libimagequant (optional, default `100`)
|
||||
- `options.colours` **[Number][8]** maximum number of palette entries, requires libimagequant (optional, default `256`)
|
||||
- `options.colors` **[Number][8]** alternative spelling of `options.colours`, requires libimagequant (optional, default `256`)
|
||||
- `options.dither` **[Number][8]** level of Floyd-Steinberg error diffusion, requires libimagequant (optional, default `1.0`)
|
||||
- `options.force` **[Boolean][6]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options` **[Object][6]?**
|
||||
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`)
|
||||
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`)
|
||||
- `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
|
||||
- `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
|
||||
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
|
||||
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`)
|
||||
- `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`)
|
||||
- `options.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -170,7 +177,7 @@ const data = await sharp(input)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -180,12 +187,14 @@ Use these WebP options for output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?** output options
|
||||
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
|
||||
- `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.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
|
||||
- `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`)
|
||||
- `options.nearLossless` **[Boolean][7]** use near_lossless compression mode (optional, default `false`)
|
||||
- `options.smartSubsample` **[Boolean][7]** use high quality chroma subsampling (optional, default `false`)
|
||||
- `options.reductionEffort` **[Number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
|
||||
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -196,7 +205,7 @@ const data = await sharp(input)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -206,18 +215,18 @@ Use these TIFF options for output image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][5]?** output options
|
||||
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.force` **[Boolean][6]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options.compression` **[Boolean][6]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
|
||||
- `options.predictor` **[Boolean][6]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
|
||||
- `options.pyramid` **[Boolean][6]** write an image pyramid (optional, default `false`)
|
||||
- `options.tile` **[Boolean][6]** write a tiled tiff (optional, default `false`)
|
||||
- `options.tileWidth` **[Boolean][6]** horizontal tile size (optional, default `256`)
|
||||
- `options.tileHeight` **[Boolean][6]** vertical tile size (optional, default `256`)
|
||||
- `options.xres` **[Number][8]** horizontal resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.yres` **[Number][8]** vertical resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.squash` **[Boolean][6]** squash 8-bit images down to 1 bit (optional, default `false`)
|
||||
- `options` **[Object][6]?** output options
|
||||
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
|
||||
- `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
|
||||
- `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
|
||||
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`)
|
||||
- `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`)
|
||||
- `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`)
|
||||
- `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`)
|
||||
- `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.yres` **[Number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
|
||||
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -232,7 +241,30 @@ sharp('input.svg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid options
|
||||
- Throws **[Error][4]** 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][6]?** output options
|
||||
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
|
||||
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`)
|
||||
|
||||
|
||||
- Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -257,8 +289,8 @@ Force output to a given format.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `format` **([String][1] \| [Object][5])** as a String or an Object with an 'id' attribute
|
||||
- `options` **[Object][5]** output options
|
||||
- `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute
|
||||
- `options` **[Object][6]** output options
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -269,7 +301,7 @@ const data = await sharp(input)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** unsupported format or options
|
||||
- Throws **[Error][4]** unsupported format or options
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -283,13 +315,15 @@ 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][6]?**
|
||||
- `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.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.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.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -305,22 +339,26 @@ sharp('input.tiff')
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][3]** Invalid parameters
|
||||
- Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
[1]: #withmetadata
|
||||
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[7]: https://nodejs.org/api/buffer.html
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
[8]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[10]: https://www.npmjs.org/package/color
|
||||
|
||||
@@ -163,11 +163,11 @@ Extract a region of the image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][9]**
|
||||
- `options` **[Object][9]** describes the region to extract using integral pixel values
|
||||
- `options.left` **[Number][8]** zero-indexed offset from left edge
|
||||
- `options.top` **[Number][8]** zero-indexed offset from top edge
|
||||
- `options.width` **[Number][8]** dimension of extracted image
|
||||
- `options.height` **[Number][8]** dimension of extracted image
|
||||
- `options.width` **[Number][8]** width of region to extract
|
||||
- `options.height` **[Number][8]** height of region to extract
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -196,6 +196,8 @@ Returns **Sharp**
|
||||
## trim
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
@@ -1,9 +1,104 @@
|
||||
# Changelog
|
||||
|
||||
### v0.23 - "*vision*"
|
||||
|
||||
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
|
||||
|
||||
* Ensure `sharp.format.vips` is present and correct (filesystem only).
|
||||
[#1813](https://github.com/lovell/sharp/issues/1813)
|
||||
|
||||
* Ensure invalid `width` and `height` provided as options to `resize` throw.
|
||||
[#1817](https://github.com/lovell/sharp/issues/1817)
|
||||
|
||||
* Allow use of 'heic' and 'heif' identifiers with `toFormat`.
|
||||
[#1834](https://github.com/lovell/sharp/pull/1834)
|
||||
[@jaubourg](https://github.com/jaubourg)
|
||||
|
||||
* Add `premultiplied` option to `composite` operation.
|
||||
[#1835](https://github.com/lovell/sharp/pull/1835)
|
||||
[@Andargor](https://github.com/Andargor)
|
||||
|
||||
* Allow instance reuse with differing `toBuffer` options.
|
||||
[#1860](https://github.com/lovell/sharp/pull/1860)
|
||||
[@RaboliotTheGrey](https://github.com/RaboliotTheGrey)
|
||||
|
||||
* Ensure image is at least 3x3 pixels before attempting trim operation.
|
||||
|
||||
#### 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.
|
||||
|
||||
#### v0.22.1 - 25<sup>th</sup> April 2019
|
||||
|
||||
* Add `modulate` operation for brightness, saturation and hue.
|
||||
[#1601](https://github.com/lovell/sharp/pull/1601)
|
||||
[@Goues](https://github.com/Goues)
|
||||
|
||||
* Improve help messaging should `require("sharp")` fail.
|
||||
[#1638](https://github.com/lovell/sharp/pull/1638)
|
||||
[@sidharthachatterjee](https://github.com/sidharthachatterjee)
|
||||
|
||||
* Add support for Node 12.
|
||||
[#1668](https://github.com/lovell/sharp/issues/1668)
|
||||
|
||||
#### v0.22.0 - 18<sup>th</sup> March 2019
|
||||
|
||||
* Remove functions previously deprecated in v0.21.0:
|
||||
|
||||
@@ -7,7 +7,8 @@ is to convert large images in common formats to
|
||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||
|
||||
Resizing an image is typically 4x-5x faster than using the
|
||||
quickest ImageMagick and GraphicsMagick settings.
|
||||
quickest ImageMagick and GraphicsMagick settings
|
||||
due to its use of [libvips](https://github.com/libvips/libvips).
|
||||
|
||||
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||
Lanczos resampling ensures quality is not sacrificed for speed.
|
||||
@@ -16,7 +17,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 and 10
|
||||
Node versions 8, 10, 12 and 13
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
@@ -64,7 +65,7 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Credits
|
||||
@@ -124,6 +125,12 @@ the help and code contributions of the following people:
|
||||
* [Julian Aubourg](https://github.com/jaubourg)
|
||||
* [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)
|
||||
* [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!
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ yarn add sharp
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Node v4.5.0+
|
||||
* Node.js v8.5.0+
|
||||
|
||||
### Building from source
|
||||
|
||||
Pre-compiled binaries for sharp are provided for use with
|
||||
Node versions 6, 8, 10 and 11 on
|
||||
Node versions 8, 10, 12 and 13 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 and 10)
|
||||
* 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,12 +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/main
|
||||
apk add --upgrade --no-cache vips-dev build-base \
|
||||
--repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/
|
||||
```
|
||||
|
||||
The smaller stack size of musl libc means
|
||||
@@ -97,7 +98,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 +151,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 +161,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.
|
||||
@@ -245,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 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
|
||||
|
||||
This module is licensed under the terms of the
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
|
||||
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
|
||||
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
|
||||
* Node.js v10.11.0
|
||||
* Node.js v12.10.0
|
||||
|
||||
### The contenders
|
||||
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.5.3 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.0.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.3 - Wrapper around libmagick++, supports Buffers only.
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.8.4 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.3.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
||||
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* sharp v0.21.0 / libvips v8.7.0 - Caching within libvips disabled to ensure a fair comparison.
|
||||
* sharp v0.23.1 / libvips v8.8.1 - Caching within libvips disabled to ensure a fair comparison.
|
||||
|
||||
### The task
|
||||
|
||||
@@ -25,14 +24,14 @@ then compress to JPEG at a "quality" setting of 80.
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 0.71 | 1.0 |
|
||||
| mapnik | buffer | buffer | 3.32 | 4.7 |
|
||||
| gm | buffer | buffer | 3.97 | 5.6 |
|
||||
| imagemagick-native | buffer | buffer | 4.06 | 5.7 |
|
||||
| imagemagick | file | file | 4.24 | 6.0 |
|
||||
| sharp | stream | stream | 25.30 | 35.6 |
|
||||
| sharp | file | file | 26.17 | 36.9 |
|
||||
| sharp | buffer | buffer | 26.45 | 37.3 |
|
||||
| jimp | buffer | buffer | 0.66 | 1.0 |
|
||||
| mapnik | buffer | buffer | 3.31 | 5.0 |
|
||||
| gm | buffer | buffer | 3.79 | 5.7 |
|
||||
| gm | file | file | 3.82 | 5.8 |
|
||||
| imagemagick | file | file | 4.17 | 6.3 |
|
||||
| sharp | stream | stream | 25.81 | 39.1 |
|
||||
| sharp | file | file | 26.76 | 40.5 |
|
||||
| sharp | buffer | buffer | 28.06 | 42.5 |
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
|
||||
@@ -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');
|
||||
@@ -20,6 +19,9 @@ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SH
|
||||
|
||||
const fail = function (err) {
|
||||
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', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
|
||||
process.exit(1);
|
||||
@@ -64,7 +66,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
|
||||
@@ -80,14 +82,16 @@ try {
|
||||
npmLog.info('sharp', `Downloading ${url}`);
|
||||
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
||||
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
|
||||
.on('error', fail)
|
||||
@@ -97,7 +101,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);
|
||||
|
||||
@@ -20,15 +20,18 @@ function env (key) {
|
||||
|
||||
module.exports = function () {
|
||||
try {
|
||||
const proxy = url.parse(proxies.map(env).find(is.string));
|
||||
const proxy = new url.URL(proxies.map(env).find(is.string));
|
||||
const tunnel = proxy.protocol === 'https:'
|
||||
? tunnelAgent.httpsOverHttps
|
||||
: tunnelAgent.httpsOverHttp;
|
||||
const proxyAuth = proxy.username && proxy.password
|
||||
? `${proxy.username}:${proxy.password}`
|
||||
: null;
|
||||
return tunnel({
|
||||
proxy: {
|
||||
port: Number(proxy.port),
|
||||
host: proxy.hostname,
|
||||
proxyAuth: proxy.auth
|
||||
proxyAuth
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -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;
|
||||
@@ -83,18 +83,22 @@ function toColorspace (colorspace) {
|
||||
* Update a colour attribute of the this.options Object.
|
||||
* @private
|
||||
* @param {String} key
|
||||
* @param {String|Object} val
|
||||
* @throws {Error} Invalid key
|
||||
* @param {String|Object} value
|
||||
* @throws {Error} Invalid value
|
||||
*/
|
||||
function _setColourOption (key, val) {
|
||||
if (is.object(val) || is.string(val)) {
|
||||
const colour = color(val);
|
||||
this.options[key] = [
|
||||
colour.red(),
|
||||
colour.green(),
|
||||
colour.blue(),
|
||||
Math.round(colour.alpha() * 255)
|
||||
];
|
||||
function _setBackgroundColourOption (key, value) {
|
||||
if (is.defined(value)) {
|
||||
if (is.object(value) || is.string(value)) {
|
||||
const colour = color(value);
|
||||
this.options[key] = [
|
||||
colour.red(),
|
||||
colour.green(),
|
||||
colour.blue(),
|
||||
Math.round(colour.alpha() * 255)
|
||||
];
|
||||
} else {
|
||||
throw is.invalidParameterError('background', 'object or string', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +115,7 @@ module.exports = function (Sharp) {
|
||||
toColourspace,
|
||||
toColorspace,
|
||||
// Private
|
||||
_setColourOption
|
||||
_setBackgroundColourOption
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.colourspace = colourspace;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const deprecate = require('util').deprecate;
|
||||
|
||||
const is = require('./is');
|
||||
|
||||
/**
|
||||
@@ -72,22 +70,23 @@ const blend = {
|
||||
* });
|
||||
*
|
||||
* @param {Object[]} images - Ordered list of images to composite
|
||||
* @param {Buffer|String} [images[].input] - Buffer containing image data or String containing the path to an image file.
|
||||
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
|
||||
* @param {Object} [images[].input.create] - describes a blank overlay to be created.
|
||||
* @param {Number} [images[].input.create.width]
|
||||
* @param {Number} [images[].input.create.height]
|
||||
* @param {Number} [images[].input.create.channels] - 3-4
|
||||
* @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
|
||||
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
|
||||
* @param {Number} [images[].top] - the pixel offset from the top edge.
|
||||
* @param {Number} [images[].left] - the pixel offset from the left edge.
|
||||
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
|
||||
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option.
|
||||
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
|
||||
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
|
||||
* @param {Number} [images[].raw.width]
|
||||
* @param {Number} [images[].raw.height]
|
||||
* @param {Number} [images[].raw.channels]
|
||||
* @param {Object} [images[].create] - describes a blank overlay to be created.
|
||||
* @param {Number} [images[].create.width]
|
||||
* @param {Number} [images[].create.height]
|
||||
* @param {Number} [images[].create.channels] - 3-4
|
||||
* @param {String|Object} [images[].create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@@ -107,7 +106,8 @@ function composite (images) {
|
||||
tile: false,
|
||||
left: -1,
|
||||
top: -1,
|
||||
gravity: 0
|
||||
gravity: 0,
|
||||
premultiplied: false
|
||||
};
|
||||
if (is.defined(image.blend)) {
|
||||
if (is.string(blend[image.blend])) {
|
||||
@@ -149,26 +149,24 @@ function composite (images) {
|
||||
throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
|
||||
}
|
||||
}
|
||||
if (is.defined(image.premultiplied)) {
|
||||
if (is.bool(image.premultiplied)) {
|
||||
composite.premultiplied = image.premultiplied;
|
||||
} else {
|
||||
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
|
||||
}
|
||||
}
|
||||
|
||||
return composite;
|
||||
});
|
||||
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,13 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const stream = require('stream');
|
||||
const events = require('events');
|
||||
const is = require('./is');
|
||||
|
||||
require('./libvips').hasVendoredLibvips();
|
||||
const sharp = require('bindings')('sharp.node');
|
||||
|
||||
let sharp;
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
sharp = require('../build/Release/sharp.node');
|
||||
} catch (err) {
|
||||
// Bail early if bindings aren't available
|
||||
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
|
||||
if (/NODE_MODULE_VERSION/.test(err.message)) {
|
||||
help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
|
||||
} else if (/invalid ELF header/.test(err.message)) {
|
||||
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
|
||||
} else {
|
||||
help.push('- Remove the "node_modules/sharp" directory, run "npm install" and look for errors');
|
||||
}
|
||||
help.push(
|
||||
'- Consult the installation documentation at https://sharp.pixelplumbing.com/en/stable/install/',
|
||||
'- Search for this error at https://github.com/lovell/sharp/issues', ''
|
||||
);
|
||||
console.error(help.join('\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Use NODE_DEBUG=sharp to enable libvips warnings
|
||||
const debuglog = util.debuglog('sharp');
|
||||
@@ -58,7 +78,7 @@ const debuglog = util.debuglog('sharp');
|
||||
*
|
||||
* @param {(Buffer|String)} [input] - if present, can be
|
||||
* 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.
|
||||
* @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.
|
||||
@@ -90,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,
|
||||
@@ -139,6 +157,9 @@ const Sharp = function (input, options) {
|
||||
gammaOut: 0,
|
||||
greyscale: false,
|
||||
normalise: 0,
|
||||
brightness: 1,
|
||||
saturation: 1,
|
||||
hue: 0,
|
||||
booleanBufferIn: null,
|
||||
booleanFileIn: '',
|
||||
joinChannelIn: [],
|
||||
@@ -174,6 +195,8 @@ const Sharp = function (input, options) {
|
||||
webpAlphaQuality: 100,
|
||||
webpLossless: false,
|
||||
webpNearLossless: false,
|
||||
webpSmartSubsample: false,
|
||||
webpReductionEffort: 4,
|
||||
tiffQuality: 80,
|
||||
tiffCompression: 'jpeg',
|
||||
tiffPredictor: 'horizontal',
|
||||
@@ -184,8 +207,13 @@ 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,
|
||||
tileBackground: [255, 255, 255, 255],
|
||||
linearA: 1,
|
||||
linearB: 0,
|
||||
// Function to notify of libvips warnings
|
||||
|
||||
BIN
lib/icc/cmyk.icm
BIN
lib/icc/sRGB.icc
63
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
|
||||
@@ -65,11 +65,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
if (is.defined(inputOptions.pages)) {
|
||||
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
||||
inputDescriptor.pages = inputOptions.pages;
|
||||
} else {
|
||||
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.page)) {
|
||||
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
||||
inputDescriptor.page = inputOptions.page;
|
||||
} else {
|
||||
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
|
||||
}
|
||||
}
|
||||
// Create new image
|
||||
@@ -115,9 +119,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 +168,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 +197,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 +226,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 +238,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 +251,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 +294,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 +306,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 +319,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;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ function blur (sigma) {
|
||||
function flatten (options) {
|
||||
this.options.flatten = is.bool(options) ? options : true;
|
||||
if (is.object(options)) {
|
||||
this._setColourOption('flattenBackground', options.background);
|
||||
this._setBackgroundColourOption('flattenBackground', options.background);
|
||||
}
|
||||
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],
|
||||
@@ -415,6 +413,62 @@ function recomb (inputMatrix) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the image using brightness, saturation and hue rotation.
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* brightness: 2 // increase lightness by a factor of 2
|
||||
* });
|
||||
*
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* hue: 180 // hue-rotate by 180 degrees
|
||||
* });
|
||||
*
|
||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
* sharp(input)
|
||||
* .modulate({
|
||||
* brightness: 0.5,
|
||||
* saturation: 0.5,
|
||||
* hue: 90
|
||||
* });
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {Number} [options.brightness] Brightness multiplier
|
||||
* @param {Number} [options.saturation] Saturation multiplier
|
||||
* @param {Number} [options.hue] Degrees for hue rotation
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function modulate (options) {
|
||||
if (!is.plainObject(options)) {
|
||||
throw is.invalidParameterError('options', 'plain object', options);
|
||||
}
|
||||
if ('brightness' in options) {
|
||||
if (is.number(options.brightness) && options.brightness >= 0) {
|
||||
this.options.brightness = options.brightness;
|
||||
} else {
|
||||
throw is.invalidParameterError('brightness', 'number above zero', options.brightness);
|
||||
}
|
||||
}
|
||||
if ('saturation' in options) {
|
||||
if (is.number(options.saturation) && options.saturation >= 0) {
|
||||
this.options.saturation = options.saturation;
|
||||
} else {
|
||||
throw is.invalidParameterError('saturation', 'number above zero', options.saturation);
|
||||
}
|
||||
}
|
||||
if ('hue' in options) {
|
||||
if (is.integer(options.hue)) {
|
||||
this.options.hue = options.hue % 360;
|
||||
} else {
|
||||
throw is.invalidParameterError('hue', 'number', options.hue);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate the Sharp prototype with operation-related functions.
|
||||
* @private
|
||||
@@ -436,6 +490,7 @@ module.exports = function (Sharp) {
|
||||
threshold,
|
||||
boolean,
|
||||
linear,
|
||||
recomb
|
||||
recomb,
|
||||
modulate
|
||||
});
|
||||
};
|
||||
|
||||
342
lib/output.js
@@ -10,6 +10,9 @@ const sharp = require('../build/Release/sharp.node');
|
||||
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
|
||||
* Note that raw pixel data is only supported for buffer output.
|
||||
*
|
||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
* See {@link withMetadata} for control over this.
|
||||
*
|
||||
* A `Promise` is returned when `callback` is not provided.
|
||||
*
|
||||
* @example
|
||||
@@ -57,7 +60,11 @@ function toFile (fileOut, callback) {
|
||||
/**
|
||||
* Write output to a Buffer.
|
||||
* JPEG, PNG, WebP, TIFF and RAW output are supported.
|
||||
* By default, the format will match the input image, except GIF and SVG input which become PNG output.
|
||||
*
|
||||
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||
*
|
||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
* See {@link withMetadata} for control over this.
|
||||
*
|
||||
* `callback`, if present, gets three arguments `(err, data, info)` where:
|
||||
* - `err` is an error, if any.
|
||||
@@ -91,9 +98,9 @@ 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);
|
||||
} else if (this.options.resolveWithObject) {
|
||||
this.options.resolveWithObject = false;
|
||||
}
|
||||
return this._pipeline(is.fn(options) ? options : callback);
|
||||
}
|
||||
@@ -109,19 +116,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,13 +151,13 @@ function withMetadata (withMetadata) {
|
||||
* @param {Number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||
* @param {String} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90
|
||||
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires mozjpeg
|
||||
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires mozjpeg
|
||||
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires mozjpeg
|
||||
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
|
||||
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
|
||||
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
|
||||
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
|
||||
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
|
||||
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
|
||||
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires mozjpeg
|
||||
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
|
||||
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable
|
||||
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
@@ -162,7 +169,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 +179,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 +205,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,11 +228,11 @@ function jpeg (options) {
|
||||
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9
|
||||
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
|
||||
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libimagequant
|
||||
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libimagequant
|
||||
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libimagequant
|
||||
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libimagequant
|
||||
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libimagequant
|
||||
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
|
||||
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
|
||||
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
|
||||
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
|
||||
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
|
||||
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
@@ -239,7 +246,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 +297,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 +308,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 +324,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 +371,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 +419,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 +427,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.
|
||||
*
|
||||
@@ -444,6 +496,17 @@ function raw () {
|
||||
return this._updateFormatOut('raw');
|
||||
}
|
||||
|
||||
const formats = new Map([
|
||||
['heic', 'heif'],
|
||||
['heif', 'heif'],
|
||||
['jpeg', 'jpeg'],
|
||||
['jpg', 'jpeg'],
|
||||
['png', 'png'],
|
||||
['raw', 'raw'],
|
||||
['tiff', 'tiff'],
|
||||
['webp', 'webp']
|
||||
]);
|
||||
|
||||
/**
|
||||
* Force output to a given format.
|
||||
*
|
||||
@@ -459,14 +522,11 @@ function raw () {
|
||||
* @throws {Error} unsupported format or options
|
||||
*/
|
||||
function toFormat (format, options) {
|
||||
if (is.object(format) && is.string(format.id)) {
|
||||
format = format.id;
|
||||
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
|
||||
if (!actualFormat) {
|
||||
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
|
||||
}
|
||||
if (format === 'jpg') format = 'jpeg';
|
||||
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
|
||||
throw new Error('Unsupported output format ' + format);
|
||||
}
|
||||
return this[format](options);
|
||||
return this[actualFormat](options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,79 +547,90 @@ 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|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 {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;
|
||||
this.options.tileOverlap = options.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);
|
||||
}
|
||||
}
|
||||
|
||||
// Background colour
|
||||
this._setBackgroundColourOption('tileBackground', options.background);
|
||||
// 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 +661,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 +670,7 @@ function _setBooleanOption (key, val) {
|
||||
* @private
|
||||
*/
|
||||
function _read () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.options.streamOut) {
|
||||
this.options.streamOut = true;
|
||||
this._pipeline();
|
||||
@@ -611,14 +683,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 +700,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 +732,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 +750,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 +781,7 @@ module.exports = function (Sharp) {
|
||||
png,
|
||||
webp,
|
||||
tiff,
|
||||
heif,
|
||||
raw,
|
||||
toFormat,
|
||||
tile,
|
||||
|
||||
@@ -7,6 +7,7 @@ const env = process.env;
|
||||
module.exports = function () {
|
||||
const arch = env.npm_config_arch || process.arch;
|
||||
const platform = env.npm_config_platform || process.platform;
|
||||
/* istanbul ignore next */
|
||||
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
|
||||
|
||||
const platformId = [`${platform}${libc}`];
|
||||
|
||||
@@ -210,12 +210,20 @@ function resize (width, height, options) {
|
||||
}
|
||||
if (is.object(options)) {
|
||||
// Width
|
||||
if (is.integer(options.width) && options.width > 0) {
|
||||
this.options.width = options.width;
|
||||
if (is.defined(options.width)) {
|
||||
if (is.integer(options.width) && options.width > 0) {
|
||||
this.options.width = options.width;
|
||||
} else {
|
||||
throw is.invalidParameterError('width', 'positive integer', options.width);
|
||||
}
|
||||
}
|
||||
// Height
|
||||
if (is.integer(options.height) && options.height > 0) {
|
||||
this.options.height = options.height;
|
||||
if (is.defined(options.height)) {
|
||||
if (is.integer(options.height) && options.height > 0) {
|
||||
this.options.height = options.height;
|
||||
} else {
|
||||
throw is.invalidParameterError('height', 'positive integer', options.height);
|
||||
}
|
||||
}
|
||||
// Fit
|
||||
if (is.defined(options.fit)) {
|
||||
@@ -238,9 +246,7 @@ function resize (width, height, options) {
|
||||
}
|
||||
}
|
||||
// Background
|
||||
if (is.defined(options.background)) {
|
||||
this._setColourOption('resizeBackground', options.background);
|
||||
}
|
||||
this._setBackgroundColourOption('resizeBackground', options.background);
|
||||
// Kernel
|
||||
if (is.defined(options.kernel)) {
|
||||
if (is.string(kernel[options.kernel])) {
|
||||
@@ -305,9 +311,9 @@ function extend (extend) {
|
||||
this.options.extendBottom = extend.bottom;
|
||||
this.options.extendLeft = extend.left;
|
||||
this.options.extendRight = extend.right;
|
||||
this._setColourOption('extendBackground', extend.background);
|
||||
this._setBackgroundColourOption('extendBackground', extend.background);
|
||||
} else {
|
||||
throw new Error('Invalid edge extension ' + extend);
|
||||
throw is.invalidParameterError('extend', 'integer or object', extend);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -334,11 +340,11 @@ function extend (extend) {
|
||||
* // Extract a region, resize, then extract from the resized image
|
||||
* });
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} options - describes the region to extract using integral pixel values
|
||||
* @param {Number} options.left - zero-indexed offset from left edge
|
||||
* @param {Number} options.top - zero-indexed offset from top edge
|
||||
* @param {Number} options.width - dimension of extracted image
|
||||
* @param {Number} options.height - dimension of extracted image
|
||||
* @param {Number} options.width - width of region to extract
|
||||
* @param {Number} options.height - height of region to extract
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@@ -349,11 +355,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;
|
||||
@@ -361,7 +367,10 @@ function extract (options) {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
|
||||
67
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.0",
|
||||
"version": "0.23.3",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -59,12 +59,17 @@
|
||||
"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>",
|
||||
"Andargor <andargor@yahoo.com>",
|
||||
"Paul Neave <paul.neave@gmail.com>",
|
||||
"Brendan Kennedy <brenwken@gmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"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.*",
|
||||
"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-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||
"test-coverage": "./test/coverage/report.sh",
|
||||
@@ -72,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"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"binding.gyp",
|
||||
"docs/**",
|
||||
"!docs/css/**",
|
||||
"install/**",
|
||||
"lib/**",
|
||||
"src/**"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/lovell/sharp"
|
||||
@@ -93,40 +106,41 @@
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"color": "^3.1.0",
|
||||
"color": "^3.1.2",
|
||||
"detect-libc": "^1.0.3",
|
||||
"fs-copy-file-sync": "^1.1.1",
|
||||
"nan": "^2.13.1",
|
||||
"nan": "^2.14.0",
|
||||
"npmlog": "^4.1.2",
|
||||
"prebuild-install": "^5.2.5",
|
||||
"semver": "^5.6.0",
|
||||
"simple-get": "^3.0.3",
|
||||
"tar": "^4.4.8",
|
||||
"prebuild-install": "^5.3.3",
|
||||
"semver": "^6.3.0",
|
||||
"simple-get": "^3.1.0",
|
||||
"tar": "^5.0.5",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^2.6.2",
|
||||
"cc": "^1.0.2",
|
||||
"async": "^3.1.0",
|
||||
"cc": "^2.0.1",
|
||||
"decompress-zip": "^0.3.2",
|
||||
"documentation": "^9.3.1",
|
||||
"exif-reader": "^1.0.2",
|
||||
"documentation": "^12.1.4",
|
||||
"exif-reader": "^1.0.3",
|
||||
"icc": "^1.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"mock-fs": "^4.8.0",
|
||||
"nyc": "^13.3.0",
|
||||
"prebuild": "8.1.0",
|
||||
"prebuild-ci": "^2.3.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"semistandard": "^13.0.1"
|
||||
"mocha": "^6.2.2",
|
||||
"mock-fs": "^4.10.3",
|
||||
"nyc": "^14.1.1",
|
||||
"prebuild": "^9.1.1",
|
||||
"prebuild-ci": "^3.1.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"semistandard": "^14.2.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.7.4"
|
||||
"libvips": "8.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=8.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"semistandard": {
|
||||
"env": [
|
||||
@@ -136,10 +150,7 @@
|
||||
"cc": {
|
||||
"linelength": "120",
|
||||
"filter": [
|
||||
"build/c++11",
|
||||
"build/include",
|
||||
"runtime/indentation_namespace",
|
||||
"runtime/references"
|
||||
"build/include"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <mutex> // NOLINT(build/c++11)
|
||||
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
@@ -31,13 +31,13 @@ using vips::VImage;
|
||||
namespace sharp {
|
||||
|
||||
// Convenience methods to access the attributes of a v8::Object
|
||||
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr) {
|
||||
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) {
|
||||
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
|
||||
}
|
||||
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr) {
|
||||
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) {
|
||||
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
|
||||
}
|
||||
std::vector<double> AttrAsRgba(v8::Handle<v8::Object> obj, std::string attr) {
|
||||
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) {
|
||||
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr);
|
||||
std::vector<double> rgba(4);
|
||||
for (unsigned int i = 0; i < 4; i++) {
|
||||
@@ -48,7 +48,7 @@ namespace sharp {
|
||||
|
||||
// Create an InputDescriptor instance from a v8::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(
|
||||
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
|
||||
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
|
||||
) {
|
||||
Nan::HandleScope();
|
||||
InputDescriptor *descriptor = new InputDescriptor;
|
||||
@@ -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,12 +141,13 @@ 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;
|
||||
case ImageType::PPM: id = "ppm"; break;
|
||||
case ImageType::FITS: id = "fits"; break;
|
||||
case ImageType::VIPS: id = "v"; break;
|
||||
case ImageType::VIPS: id = "vips"; break;
|
||||
case ImageType::RAW: id = "raw"; break;
|
||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||
case ImageType::MISSING: id = "missing"; 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;
|
||||
}
|
||||
|
||||
|
||||
24
src/common.h
@@ -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)))
|
||||
@@ -43,7 +43,7 @@ using vips::VImage;
|
||||
|
||||
namespace sharp {
|
||||
|
||||
struct InputDescriptor {
|
||||
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
|
||||
std::string name;
|
||||
std::string file;
|
||||
char *buffer;
|
||||
@@ -77,22 +77,22 @@ namespace sharp {
|
||||
};
|
||||
|
||||
// Convenience methods to access the attributes of a v8::Object
|
||||
bool HasAttr(v8::Handle<v8::Object> obj, std::string attr);
|
||||
std::string AttrAsStr(v8::Handle<v8::Object> obj, std::string attr);
|
||||
std::vector<double> AttrAsRgba(v8::Handle<v8::Object> obj, std::string attr);
|
||||
template<typename T> v8::Local<T> AttrAs(v8::Handle<v8::Object> obj, std::string attr) {
|
||||
bool HasAttr(v8::Local<v8::Object> obj, std::string attr);
|
||||
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr);
|
||||
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr);
|
||||
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) {
|
||||
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
|
||||
}
|
||||
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, std::string attr) {
|
||||
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) {
|
||||
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
|
||||
}
|
||||
template<typename T> T AttrTo(v8::Handle<v8::Object> obj, int attr) {
|
||||
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
|
||||
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
|
||||
}
|
||||
|
||||
// Create an InputDescriptor instance from a v8::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(
|
||||
v8::Handle<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist);
|
||||
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
|
||||
|
||||
enum class ImageType {
|
||||
JPEG,
|
||||
@@ -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),
|
||||
|
||||
@@ -185,6 +185,29 @@ namespace sharp {
|
||||
0.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
|
||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue) {
|
||||
if (HasAlpha(image)) {
|
||||
// Separate alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
return RemoveAlpha(image)
|
||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||
.linear(
|
||||
{ brightness, saturation, 1},
|
||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||
)
|
||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||
.bandjoin(alpha);
|
||||
} else {
|
||||
return image
|
||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||
.linear(
|
||||
{ brightness, saturation, 1 },
|
||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
||||
)
|
||||
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||
*/
|
||||
@@ -235,17 +258,30 @@ namespace sharp {
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, double const threshold) {
|
||||
if (image.width() < 3 && image.height() < 3) {
|
||||
throw VError("Image to trim must be at least 3x3 pixels");
|
||||
}
|
||||
// Top-left pixel provides the background colour
|
||||
VImage background = image.extract_area(0, 0, 1, 1);
|
||||
if (HasAlpha(background)) {
|
||||
background = background.flatten();
|
||||
}
|
||||
int top, width, height;
|
||||
int const left = image.find_trim(&top, &width, &height, VImage::option()
|
||||
int left, top, width, height;
|
||||
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", background(0, 0))
|
||||
->set("threshold", threshold));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ namespace sharp {
|
||||
*/
|
||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
||||
|
||||
/*
|
||||
* Modulate brightness, saturation and hue
|
||||
*/
|
||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
#endif // SRC_OPERATIONS_H_
|
||||
|
||||
141
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,20 @@
|
||||
#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
|
||||
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
||||
#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 +73,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;
|
||||
@@ -75,7 +81,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
// Ignore if 0
|
||||
if (baton->limitInputPixels > 0 && image.width() * image.height() > baton->limitInputPixels) {
|
||||
if (baton->limitInputPixels > 0 &&
|
||||
static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(baton->limitInputPixels)) {
|
||||
(baton->err).append("Input image exceeds pixel limit");
|
||||
return Error();
|
||||
}
|
||||
@@ -94,9 +101,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
|
||||
@@ -300,17 +314,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));
|
||||
}
|
||||
|
||||
@@ -349,6 +361,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
||||
bool const shouldApplyMedian = baton->medianSize > 0;
|
||||
bool const shouldComposite = !baton->composite.empty();
|
||||
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
|
||||
|
||||
if (shouldComposite && !HasAlpha(image)) {
|
||||
image = sharp::EnsureAlpha(image);
|
||||
@@ -388,12 +401,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);
|
||||
@@ -476,8 +490,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));
|
||||
@@ -528,6 +542,10 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
image = sharp::Recomb(image, baton->recombMatrix);
|
||||
}
|
||||
|
||||
if (shouldModulate) {
|
||||
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
||||
}
|
||||
|
||||
// Sharpen
|
||||
if (shouldSharpen) {
|
||||
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
||||
@@ -576,7 +594,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
if (!HasAlpha(compositeImage)) {
|
||||
compositeImage = sharp::EnsureAlpha(compositeImage);
|
||||
}
|
||||
compositeImage = compositeImage.premultiply();
|
||||
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
|
||||
// Calculate position
|
||||
int left;
|
||||
int top;
|
||||
@@ -674,8 +692,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));
|
||||
}
|
||||
}
|
||||
@@ -741,6 +759,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;
|
||||
@@ -774,6 +794,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) {
|
||||
@@ -808,6 +840,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);
|
||||
@@ -852,6 +885,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) ||
|
||||
@@ -874,6 +909,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;
|
||||
@@ -892,7 +941,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 {
|
||||
@@ -911,6 +962,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
};
|
||||
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
|
||||
vips::VOption *options = VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
@@ -919,7 +975,9 @@ 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("background", baton->tileBackground)
|
||||
->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
|
||||
@@ -999,8 +1057,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;
|
||||
@@ -1135,9 +1193,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
|
||||
@@ -1184,6 +1239,7 @@ NAN_METHOD(pipeline) {
|
||||
composite->left = AttrTo<int32_t>(compositeObject, "left");
|
||||
composite->top = AttrTo<int32_t>(compositeObject, "top");
|
||||
composite->tile = AttrTo<bool>(compositeObject, "tile");
|
||||
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied");
|
||||
baton->composite.push_back(composite);
|
||||
}
|
||||
// Resize options
|
||||
@@ -1210,6 +1266,9 @@ NAN_METHOD(pipeline) {
|
||||
baton->flattenBackground = AttrAsRgba(options, "flattenBackground");
|
||||
baton->negate = AttrTo<bool>(options, "negate");
|
||||
baton->blurSigma = AttrTo<double>(options, "blurSigma");
|
||||
baton->brightness = AttrTo<double>(options, "brightness");
|
||||
baton->saturation = AttrTo<double>(options, "saturation");
|
||||
baton->hue = AttrTo<int32_t>(options, "hue");
|
||||
baton->medianSize = AttrTo<uint32_t>(options, "medianSize");
|
||||
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma");
|
||||
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat");
|
||||
@@ -1295,6 +1354,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");
|
||||
@@ -1310,12 +1371,20 @@ 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->tileBackground = AttrAsRgba(options, "tileBackground");
|
||||
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
|
||||
if (tileContainer == "zip") {
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
} else {
|
||||
|
||||
@@ -41,6 +41,7 @@ struct Composite {
|
||||
int left;
|
||||
int top;
|
||||
bool tile;
|
||||
bool premultiplied;
|
||||
|
||||
Composite():
|
||||
input(nullptr),
|
||||
@@ -48,12 +49,12 @@ struct Composite {
|
||||
gravity(0),
|
||||
left(-1),
|
||||
top(-1),
|
||||
tile(false) {}
|
||||
tile(false),
|
||||
premultiplied(false) {}
|
||||
};
|
||||
|
||||
struct PipelineBaton {
|
||||
sharp::InputDescriptor *input;
|
||||
std::string iccProfilePath;
|
||||
int limitInputPixels;
|
||||
std::string formatOut;
|
||||
std::string fileOut;
|
||||
@@ -87,6 +88,9 @@ struct PipelineBaton {
|
||||
std::vector<double> flattenBackground;
|
||||
bool negate;
|
||||
double blurSigma;
|
||||
double brightness;
|
||||
double saturation;
|
||||
int hue;
|
||||
int medianSize;
|
||||
double sharpenSigma;
|
||||
double sharpenFlat;
|
||||
@@ -135,6 +139,8 @@ struct PipelineBaton {
|
||||
int webpAlphaQuality;
|
||||
bool webpNearLossless;
|
||||
bool webpLossless;
|
||||
bool webpSmartSubsample;
|
||||
int webpReductionEffort;
|
||||
int tiffQuality;
|
||||
VipsForeignTiffCompression tiffCompression;
|
||||
VipsForeignTiffPredictor tiffPredictor;
|
||||
@@ -145,6 +151,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;
|
||||
@@ -166,6 +175,8 @@ struct PipelineBaton {
|
||||
VipsForeignDzLayout tileLayout;
|
||||
std::string tileFormat;
|
||||
int tileAngle;
|
||||
std::vector<double> tileBackground;
|
||||
int tileSkipBlanks;
|
||||
VipsForeignDzDepth tileDepth;
|
||||
std::unique_ptr<double[]> recombMatrix;
|
||||
|
||||
@@ -189,6 +200,9 @@ struct PipelineBaton {
|
||||
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||
negate(false),
|
||||
blurSigma(0.0),
|
||||
brightness(1.0),
|
||||
saturation(1.0),
|
||||
hue(0),
|
||||
medianSize(0),
|
||||
sharpenSigma(0.0),
|
||||
sharpenFlat(1.0),
|
||||
@@ -231,6 +245,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),
|
||||
@@ -241,6 +260,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),
|
||||
@@ -259,6 +281,8 @@ struct PipelineBaton {
|
||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
|
||||
tileAngle(0),
|
||||
tileBackground{ 255.0, 255.0, 255.0, 255.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);
|
||||
|
||||
@@ -150,8 +150,9 @@ 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"
|
||||
for (std::string const f : {
|
||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
||||
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
|
||||
}) {
|
||||
// Input
|
||||
Local<Boolean> hasInputFile =
|
||||
|
||||
@@ -8,17 +8,16 @@
|
||||
"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.8.4",
|
||||
"mapnik": "^4.3.1",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=8.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,6 @@ const gm = require('gm');
|
||||
const imagemagick = require('imagemagick');
|
||||
const mapnik = require('mapnik');
|
||||
const jimp = require('jimp');
|
||||
let imagemagickNative;
|
||||
try {
|
||||
imagemagickNative = require('imagemagick-native');
|
||||
} catch (err) {
|
||||
console.log('Excluding imagemagick-native');
|
||||
}
|
||||
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
@@ -28,7 +22,7 @@ const height = 588;
|
||||
sharp.cache(false);
|
||||
|
||||
async.series({
|
||||
'jpeg': function (callback) {
|
||||
jpeg: function (callback) {
|
||||
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||
const jpegSuite = new Benchmark.Suite('jpeg');
|
||||
// jimp
|
||||
@@ -126,29 +120,6 @@ async.series({
|
||||
});
|
||||
}
|
||||
});
|
||||
// imagemagick-native
|
||||
if (typeof imagemagickNative !== 'undefined') {
|
||||
jpegSuite.add('imagemagick-native-buffer-buffer', {
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
imagemagickNative.convert({
|
||||
srcData: inputJpgBuffer,
|
||||
quality: 80,
|
||||
width: width,
|
||||
height: height,
|
||||
format: 'JPEG',
|
||||
filter: 'Lanczos'
|
||||
}, function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// gm
|
||||
jpegSuite.add('gm-buffer-file', {
|
||||
defer: true,
|
||||
@@ -289,6 +260,9 @@ async.series({
|
||||
.then(function (buffer) {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}).on('cycle', function (event) {
|
||||
@@ -698,22 +672,6 @@ async.series({
|
||||
});
|
||||
}
|
||||
});
|
||||
// imagemagick-native
|
||||
if (typeof imagemagickNative !== 'undefined') {
|
||||
pngSuite.add('imagemagick-native-buffer-buffer', {
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
imagemagickNative.convert({
|
||||
srcData: inputPngBuffer,
|
||||
width: width,
|
||||
height: height,
|
||||
format: 'PNG',
|
||||
filter: 'Lanczos'
|
||||
});
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
// gm
|
||||
pngSuite.add('gm-file-file', {
|
||||
defer: true,
|
||||
|
||||
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/expected.absent.composite.premultiplied.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/expected.false.composite.premultiplied.png
vendored
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/expected.true.composite.premultiplied.png
vendored
Normal file
|
After Width: | Height: | Size: 16 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/flatten-rgb16-orange.jpg
vendored
|
Before Width: | Height: | Size: 839 B After Width: | Height: | Size: 824 B |
BIN
test/fixtures/expected/modulate-all.jpg
vendored
Normal file
|
After Width: | Height: | Size: 664 KiB |
BIN
test/fixtures/expected/modulate-brightness-0-5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
test/fixtures/expected/modulate-brightness-2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 692 KiB |
BIN
test/fixtures/expected/modulate-hue-120.jpg
vendored
Normal file
|
After Width: | Height: | Size: 653 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-120.png
vendored
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-150.png
vendored
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-180.png
vendored
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-210.png
vendored
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-240.png
vendored
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-270.png
vendored
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-30.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-300.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-330.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-360.png
vendored
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-60.png
vendored
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-90.png
vendored
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
test/fixtures/expected/modulate-linear.jpg
vendored
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
test/fixtures/expected/modulate-saturation-0.5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 606 KiB |
BIN
test/fixtures/expected/modulate-saturation-2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 672 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 |
BIN
test/fixtures/image-in-alpha.png
vendored
Normal file
|
After Width: | Height: | Size: 70 KiB |
7
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
|
||||
const fingerprint = function (image, callback) {
|
||||
sharp(image)
|
||||
.flatten('gray')
|
||||
.greyscale()
|
||||
.normalise()
|
||||
.resize(9, 8, { fit: sharp.fit.fill })
|
||||
@@ -80,9 +81,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'),
|
||||
@@ -90,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
|
||||
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)
|
||||
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
|
||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||
@@ -120,6 +119,8 @@ module.exports = {
|
||||
outputTiff: getPath('output.tiff'),
|
||||
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
|
||||
|
||||
testPattern: getPath('test-pattern.png'),
|
||||
|
||||
// Path for tests requiring human inspection
|
||||
path: getPath,
|
||||
|
||||
|
||||