Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b609df4b48 | ||
|
|
c567d3b9ab | ||
|
|
27d9fe2a4e | ||
|
|
ac883c5215 | ||
|
|
e8720c9374 | ||
|
|
42e45d842a | ||
|
|
72fd8abe2c | ||
|
|
ac18bbbc7c | ||
|
|
fcbe4e1e01 | ||
|
|
9280742385 | ||
|
|
7a1a1cf9e8 | ||
|
|
ea599ade10 | ||
|
|
1de49f3ed8 | ||
|
|
4ac65054bc | ||
|
|
23033e2050 | ||
|
|
dd3b78272a | ||
|
|
80d169b7c2 | ||
|
|
003279a0b0 | ||
|
|
af80d7e389 | ||
|
|
21a960796c | ||
|
|
fc3b4a683d | ||
|
|
808133e7dc | ||
|
|
801b6fea6c | ||
|
|
c2ecde6a16 | ||
|
|
55efe5602b | ||
|
|
c62002554b | ||
|
|
7f83ecd255 | ||
|
|
dc5f4dcd28 | ||
|
|
735793ba99 | ||
|
|
47792df689 | ||
|
|
5c6cdfaece | ||
|
|
115a6b10f6 | ||
|
|
4feee506cf | ||
|
|
83db5f8a2a | ||
|
|
7eb5efa3a3 | ||
|
|
5a9f89fe06 | ||
|
|
02e0c2dfc9 | ||
|
|
968d9d7008 | ||
|
|
7faacd91b0 | ||
|
|
154eaff4ec | ||
|
|
424660278d | ||
|
|
2b01951306 |
@@ -1,15 +1,15 @@
|
|||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-13-0-snap
|
image_family: freebsd-14-0-snap
|
||||||
|
|
||||||
task:
|
task:
|
||||||
name: FreeBSD 13.0
|
name: FreeBSD
|
||||||
env:
|
env:
|
||||||
IGNORE_OSVERSION: yes
|
IGNORE_OSVERSION: yes
|
||||||
skip_notifications: true
|
skip_notifications: true
|
||||||
prerequisites_script:
|
prerequisites_script:
|
||||||
- pkg update -f
|
- pkg update -f
|
||||||
- pkg upgrade -y
|
- pkg upgrade -y
|
||||||
- pkg install -y pkgconf vips node npm
|
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
|
||||||
install_script:
|
install_script:
|
||||||
- npm install --build-from-source --unsafe-perm
|
- npm install --build-from-source --unsafe-perm
|
||||||
test_script:
|
test_script:
|
||||||
|
|||||||
10
.github/CONTRIBUTING.md
vendored
@@ -23,22 +23,22 @@ the feature you need.
|
|||||||
|
|
||||||
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
|
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
|
||||||
|
|
||||||
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
|
Please select the `main` branch as the destination for your Pull Request so your fix can be included in the next minor release.
|
||||||
|
|
||||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.
|
||||||
|
|
||||||
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
|
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
|
||||||
|
|
||||||
## Submit a Pull Request with a new feature
|
## Submit a Pull Request with a new feature
|
||||||
|
|
||||||
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
|
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
|
||||||
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
|
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
|
||||||
|
|
||||||
Where possible, the functional tests use gradient-based perceptual hashes
|
Where possible, the functional tests use gradient-based perceptual hashes
|
||||||
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
|
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
|
||||||
to compare expected vs actual images.
|
to compare expected vs actual images.
|
||||||
|
|
||||||
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
|
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/main/package.json#L5).
|
||||||
|
|
||||||
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
||||||
|
|
||||||
@@ -93,5 +93,5 @@ Please feel free to ask any questions via a
|
|||||||
[new issue](https://github.com/lovell/sharp/issues/new).
|
[new issue](https://github.com/lovell/sharp/issues/new).
|
||||||
|
|
||||||
If you're unable to post details publicly, please
|
If you're unable to post details publicly, please
|
||||||
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L5)
|
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
|
||||||
for private, paid consulting.
|
for private, paid consulting.
|
||||||
|
|||||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -5,12 +5,24 @@ labels: enhancement
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
What are you trying to achieve?
|
## Feature request
|
||||||
|
|
||||||
Have you searched for similar feature requests?
|
### What are you trying to achieve?
|
||||||
|
|
||||||
What would you expect the API to look like?
|
<!-- Please provide context here. -->
|
||||||
|
|
||||||
What alternatives have you considered?
|
### When you searched for similar feature requests, what did you find that might be related?
|
||||||
|
|
||||||
Is there a sample image that helps explain?
|
<!-- Please demonstrate your research here. -->
|
||||||
|
|
||||||
|
### What would you expect the API to look like?
|
||||||
|
|
||||||
|
<!-- Please provide your suggestions here. -->
|
||||||
|
|
||||||
|
### What alternatives have you considered?
|
||||||
|
|
||||||
|
<!-- Please provide your ideas here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this feature
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
|
|||||||
35
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -5,18 +5,41 @@ labels: installation
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
|
<!-- Please try to answer as many of these questions as possible. -->
|
||||||
|
|
||||||
Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
|
## Possible install-time or require-time problem
|
||||||
|
|
||||||
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
|
||||||
|
- [ ] I have ensured that the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime.
|
||||||
|
|
||||||
|
### Are you using the latest version of sharp?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
|
||||||
|
|
||||||
|
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
|
||||||
|
|
||||||
|
### Is this a problem with filesystem permissions?
|
||||||
|
|
||||||
If you are using npm v6 or earlier and installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
|
If you are using npm v6 or earlier and installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
|
||||||
|
|
||||||
If you are using npm v7, does the user running `npm install` own the directory it is run in?
|
If you are using npm v7 or later, does the user running `npm install` own the directory it is run in?
|
||||||
|
|
||||||
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
|
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
|
||||||
|
|
||||||
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
|
### What is the complete output of running `npm install --verbose sharp`?
|
||||||
|
|
||||||
What is the output of running `npx envinfo --binaries --system`?
|
<details>
|
||||||
|
|
||||||
|
<!-- Please provide output of `npm install --verbose sharp` here. -->
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
|
||||||
|
|
||||||
|
<!-- Please provide output of `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp` here. -->
|
||||||
|
|||||||
41
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
@@ -7,14 +7,43 @@ labels: triage
|
|||||||
|
|
||||||
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
||||||
|
|
||||||
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
## Possible bug
|
||||||
|
|
||||||
What are the steps to reproduce?
|
### Is this a possible bug in a feature of sharp, unrelated to installation?
|
||||||
|
|
||||||
What is the expected behaviour?
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
|
- [ ] Running `npm install sharp` completes without error.
|
||||||
|
- [ ] Running `node -e "require('sharp')"` completes without error.
|
||||||
|
|
||||||
Are you able to provide a sample image that helps explain the problem?
|
If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.
|
||||||
|
|
||||||
What is the output of running `npx envinfo --binaries --system`?
|
### Are you using the latest version of sharp?
|
||||||
|
|
||||||
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
|
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
|
||||||
|
|
||||||
|
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
|
||||||
|
|
||||||
|
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
|
||||||
|
|
||||||
|
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
|
||||||
|
|
||||||
|
<!-- Please provide output of the above command here. -->
|
||||||
|
|
||||||
|
### What are the steps to reproduce?
|
||||||
|
|
||||||
|
<!-- Please enter steps to reproduce here. -->
|
||||||
|
|
||||||
|
### What is the expected behaviour?
|
||||||
|
|
||||||
|
<!-- Please enter the expected behaviour here. -->
|
||||||
|
|
||||||
|
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem
|
||||||
|
|
||||||
|
<!-- Please provide either formatted code or a link to a repo/gist that allows someone else to reproduce here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this problem
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
|
|||||||
18
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -7,10 +7,20 @@ labels: question
|
|||||||
|
|
||||||
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
|
<!-- 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?
|
## Question about an existing feature
|
||||||
|
|
||||||
Have you searched for similar questions?
|
### What are you trying to achieve?
|
||||||
|
|
||||||
Are you able to provide a minimal, standalone code sample that demonstrates this question?
|
<!-- Please provide context here. -->
|
||||||
|
|
||||||
Are you able to provide a sample image that helps explain the question?
|
### When you searched for similar issues, what did you find that might be related?
|
||||||
|
|
||||||
|
<!-- Please demonstrate your research here. -->
|
||||||
|
|
||||||
|
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question
|
||||||
|
|
||||||
|
<!-- Please provide either formatted code or a link to a repo/gist that helps someone else understand here. -->
|
||||||
|
|
||||||
|
### Please provide sample image(s) that help explain this question
|
||||||
|
|
||||||
|
<!-- Please provide links to one or more images here. -->
|
||||||
|
|||||||
19
.github/workflows/ci.yml
vendored
@@ -33,17 +33,33 @@ jobs:
|
|||||||
- os: macos-10.15
|
- os: macos-10.15
|
||||||
nodejs_version: 12
|
nodejs_version: 12
|
||||||
prebuild: true
|
prebuild: true
|
||||||
|
nodejs_arch: x64
|
||||||
- os: macos-10.15
|
- os: macos-10.15
|
||||||
nodejs_version: 14
|
nodejs_version: 14
|
||||||
|
nodejs_arch: x64
|
||||||
- os: macos-10.15
|
- os: macos-10.15
|
||||||
nodejs_version: 16
|
nodejs_version: 16
|
||||||
|
nodejs_arch: x64
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
nodejs_version: 12
|
nodejs_version: 12
|
||||||
|
nodejs_arch: x86
|
||||||
prebuild: true
|
prebuild: true
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
nodejs_version: 14
|
nodejs_version: 14
|
||||||
|
nodejs_arch: x86
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
nodejs_version: 16
|
nodejs_version: 16
|
||||||
|
nodejs_arch: x86
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 12
|
||||||
|
nodejs_arch: x64
|
||||||
|
prebuild: true
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 14
|
||||||
|
nodejs_arch: x64
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 16
|
||||||
|
nodejs_arch: x64
|
||||||
steps:
|
steps:
|
||||||
- name: Dependencies (Linux glibc)
|
- name: Dependencies (Linux glibc)
|
||||||
if: contains(matrix.container, 'centos')
|
if: contains(matrix.container, 'centos')
|
||||||
@@ -57,9 +73,10 @@ jobs:
|
|||||||
run: apk add build-base git python3 --update-cache
|
run: apk add build-base git python3 --update-cache
|
||||||
- name: Dependencies (macOS, Windows)
|
- name: Dependencies (macOS, Windows)
|
||||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.nodejs_version }}
|
node-version: ${{ matrix.nodejs_version }}
|
||||||
|
architecture: ${{ matrix.nodejs_arch }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Fix working directory ownership
|
- name: Fix working directory ownership
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
@@ -95,10 +95,10 @@ readableStream
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||||
covers reporting bugs, requesting features and submitting code changes.
|
covers reporting bugs, requesting features and submitting code changes.
|
||||||
|
|
||||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
[](https://coveralls.io/r/lovell/sharp?branch=main)
|
||||||
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|||||||
17
appveyor.yml
@@ -1,17 +0,0 @@
|
|||||||
os: Visual Studio 2019
|
|
||||||
version: "{build}"
|
|
||||||
build: off
|
|
||||||
platform: x86
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- nodejs_version: "12"
|
|
||||||
prebuild: true
|
|
||||||
- nodejs_version: "14"
|
|
||||||
- nodejs_version: "16"
|
|
||||||
install:
|
|
||||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
|
|
||||||
- npm install --build-from-source
|
|
||||||
test_script:
|
|
||||||
- npm test
|
|
||||||
on_success:
|
|
||||||
- if [%prebuild%] == [true] if [%APPVEYOR_REPO_TAG%] == [true] npx prebuild --runtime napi --target 5
|
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
'VCCLCompilerTool': {
|
'VCCLCompilerTool': {
|
||||||
'ExceptionHandling': 1,
|
'ExceptionHandling': 1,
|
||||||
'Optimization': 1,
|
'Optimization': 1,
|
||||||
'RuntimeLibrary': '2', # /MD
|
|
||||||
'WholeProgramOptimization': 'true'
|
'WholeProgramOptimization': 'true'
|
||||||
},
|
},
|
||||||
'VCLibrarianTool': {
|
'VCLibrarianTool': {
|
||||||
@@ -206,7 +205,6 @@
|
|||||||
'VCCLCompilerTool': {
|
'VCCLCompilerTool': {
|
||||||
'ExceptionHandling': 1,
|
'ExceptionHandling': 1,
|
||||||
'Optimization': 1,
|
'Optimization': 1,
|
||||||
'RuntimeLibrary': '2', # /MD
|
|
||||||
'WholeProgramOptimization': 'true'
|
'WholeProgramOptimization': 'true'
|
||||||
},
|
},
|
||||||
'VCLibrarianTool': {
|
'VCLibrarianTool': {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
@@ -65,7 +65,7 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
|
|||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||||
covers reporting bugs, requesting features and submitting code changes.
|
covers reporting bugs, requesting features and submitting code changes.
|
||||||
|
|
||||||
### Licensing
|
### Licensing
|
||||||
|
|||||||
@@ -9,7 +9,13 @@ An alpha channel may be present and will be unchanged by the operation.
|
|||||||
|
|
||||||
* `rgb` **([string][1] | [Object][2])** parsed by the [color][3] module to extract chroma values.
|
* `rgb` **([string][1] | [Object][2])** parsed by the [color][3] module to extract chroma values.
|
||||||
|
|
||||||
<!---->
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input)
|
||||||
|
.tint({ r: 255, g: 240, b: 16 })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][4]** Invalid parameter
|
* Throws **[Error][4]** Invalid parameter
|
||||||
|
|
||||||
@@ -28,6 +34,12 @@ An alpha channel may be present, and will be unchanged by the operation.
|
|||||||
|
|
||||||
* `greyscale` **[Boolean][5]** (optional, default `true`)
|
* `greyscale` **[Boolean][5]** (optional, default `true`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).greyscale().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## grayscale
|
## grayscale
|
||||||
|
|||||||
@@ -41,11 +41,29 @@ and [https://www.cairographics.org/operators/][2]
|
|||||||
* `images[].raw.width` **[Number][7]?**
|
* `images[].raw.width` **[Number][7]?**
|
||||||
* `images[].raw.height` **[Number][7]?**
|
* `images[].raw.height` **[Number][7]?**
|
||||||
* `images[].raw.channels` **[Number][7]?**
|
* `images[].raw.channels` **[Number][7]?**
|
||||||
|
* `images[].animated` **[boolean][9]** Set to `true` to read all frames/pages of an animated image. (optional, default `false`)
|
||||||
* `images[].failOnError` **[boolean][9]** @see [constructor parameters][10] (optional, default `true`)
|
* `images[].failOnError` **[boolean][9]** @see [constructor parameters][10] (optional, default `true`)
|
||||||
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)
|
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await sharp(background)
|
||||||
|
.composite([
|
||||||
|
{ input: layer1, gravity: 'northwest' },
|
||||||
|
{ input: layer2, gravity: 'southeast' },
|
||||||
|
])
|
||||||
|
.toFile('combined.png');
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp('input.gif', { animated: true })
|
||||||
|
.composite([
|
||||||
|
{ input: 'overlay.png', tile: true, blend: 'saturate' }
|
||||||
|
])
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.png')
|
sharp('input.png')
|
||||||
.rotate(180)
|
.rotate(180)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ A `Promise` is returned when `callback` is not provided.
|
|||||||
|
|
||||||
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
* `size`: Total size of image in bytes, for Stream and Buffer input only
|
* `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
|
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
|
||||||
* `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
* `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
|
||||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
|
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
|
||||||
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||||
* `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
|
* `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
|
||||||
@@ -63,6 +63,18 @@ image
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Based on EXIF rotation metadata, get the right-side-up width and height:
|
||||||
|
|
||||||
|
const size = getNormalSize(await sharp(input).metadata());
|
||||||
|
|
||||||
|
function getNormalSize({ width, height, orientation }) {
|
||||||
|
return orientation || 0 >= 5
|
||||||
|
? { width: height, height: width }
|
||||||
|
: { width, height };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Returns **([Promise][5]<[Object][6]> | Sharp)**
|
Returns **([Promise][5]<[Object][6]> | Sharp)**
|
||||||
|
|
||||||
## stats
|
## stats
|
||||||
@@ -82,9 +94,9 @@ A `Promise` is returned when `callback` is not provided.
|
|||||||
* `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
* `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
||||||
* `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
* `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
||||||
* `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
* `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
|
||||||
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
|
||||||
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
|
||||||
|
|
||||||
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
||||||
written to a buffer in order to run `stats` on the result (see third example).
|
written to a buffer in order to run `stats` on the result (see third example).
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
|||||||
|
|
||||||
* `flip` **[Boolean][6]** (optional, default `true`)
|
* `flip` **[Boolean][6]** (optional, default `true`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).flip().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## flop
|
## flop
|
||||||
@@ -64,6 +70,12 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
|||||||
|
|
||||||
* `flop` **[Boolean][6]** (optional, default `true`)
|
* `flop` **[Boolean][6]** (optional, default `true`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).flop().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## affine
|
## affine
|
||||||
@@ -129,13 +141,43 @@ When used without parameters, performs a fast, mild sharpen of the output image.
|
|||||||
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
||||||
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
||||||
|
|
||||||
|
See [libvips sharpen][8] operation.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
* `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
* `options` **([Object][2] | [number][1])?** if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
|
||||||
* `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
|
|
||||||
* `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
|
|
||||||
|
|
||||||
<!---->
|
* `options.sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||||
|
* `options.m1` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
|
||||||
|
* `options.m2` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
|
||||||
|
* `options.x1` **[number][1]** threshold between "flat" and "jagged" (optional, default `2.0`)
|
||||||
|
* `options.y2` **[number][1]** maximum amount of brightening. (optional, default `10.0`)
|
||||||
|
* `options.y3` **[number][1]** maximum amount of darkening. (optional, default `20.0`)
|
||||||
|
* `flat` **[number][1]?** (deprecated) see `options.m1`.
|
||||||
|
* `jagged` **[number][1]?** (deprecated) see `options.m2`.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const data = await sharp(input).sharpen().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const data = await sharp(input)
|
||||||
|
.sharpen({
|
||||||
|
sigma: 2,
|
||||||
|
m1: 0
|
||||||
|
m2: 3,
|
||||||
|
x1: 3,
|
||||||
|
y2: 15,
|
||||||
|
y3: 15,
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][5]** Invalid parameters
|
* Throws **[Error][5]** Invalid parameters
|
||||||
|
|
||||||
@@ -150,7 +192,15 @@ When used without parameters the default window is 3x3.
|
|||||||
|
|
||||||
* `size` **[number][1]** square mask size: size x size (optional, default `3`)
|
* `size` **[number][1]** square mask size: size x size (optional, default `3`)
|
||||||
|
|
||||||
<!---->
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).median().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).median(5).toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][5]** Invalid parameters
|
* Throws **[Error][5]** Invalid parameters
|
||||||
|
|
||||||
@@ -190,7 +240,7 @@ Returns **Sharp**
|
|||||||
|
|
||||||
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||||
|
|
||||||
See also [removeAlpha][8].
|
See also [removeAlpha][9].
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
@@ -249,6 +299,12 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
|
|||||||
|
|
||||||
* `normalise` **[Boolean][6]** (optional, default `true`)
|
* `normalise` **[Boolean][6]** (optional, default `true`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).normalise().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## normalize
|
## normalize
|
||||||
@@ -259,12 +315,18 @@ Alternative spelling of normalise.
|
|||||||
|
|
||||||
* `normalize` **[Boolean][6]** (optional, default `true`)
|
* `normalize` **[Boolean][6]** (optional, default `true`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input).normalize().toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## clahe
|
## clahe
|
||||||
|
|
||||||
Perform contrast limiting adaptive histogram equalization
|
Perform contrast limiting adaptive histogram equalization
|
||||||
[CLAHE][9].
|
[CLAHE][10].
|
||||||
|
|
||||||
This will, in general, enhance the clarity of the image by bringing out darker details.
|
This will, in general, enhance the clarity of the image by bringing out darker details.
|
||||||
|
|
||||||
@@ -278,7 +340,16 @@ This will, in general, enhance the clarity of the image by bringing out darker d
|
|||||||
cumulative histogram. A value of 0 disables contrast limiting. Valid values
|
cumulative histogram. A value of 0 disables contrast limiting. Valid values
|
||||||
are integers in the range 0-100 (inclusive) (optional, default `3`)
|
are integers in the range 0-100 (inclusive) (optional, default `3`)
|
||||||
|
|
||||||
<!---->
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const output = await sharp(input)
|
||||||
|
.clahe({
|
||||||
|
width: 3,
|
||||||
|
height: 3,
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][5]** Invalid parameters
|
* Throws **[Error][5]** Invalid parameters
|
||||||
|
|
||||||
@@ -349,7 +420,7 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
|
|||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
* `operand` **([Buffer][10] | [string][3])** Buffer containing image data or string containing the path to an image file.
|
* `operand` **([Buffer][11] | [string][3])** Buffer containing image data or string containing the path to an image file.
|
||||||
* `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
* `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
|
||||||
* `options` **[Object][2]?**
|
* `options` **[Object][2]?**
|
||||||
|
|
||||||
@@ -430,28 +501,41 @@ brightness is multiplicative whereas lightness is additive.
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(input)
|
// increase brightness by a factor of 2
|
||||||
|
const output = await sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
brightness: 2 // increase brightness by a factor of 2
|
brightness: 2
|
||||||
});
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
sharp(input)
|
```javascript
|
||||||
|
// hue-rotate by 180 degrees
|
||||||
|
const output = await sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
hue: 180 // hue-rotate by 180 degrees
|
hue: 180
|
||||||
});
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
sharp(input)
|
```javascript
|
||||||
|
// increase lightness by +50
|
||||||
|
const output = await sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
lightness: 50 // increase lightness by +50
|
lightness: 50
|
||||||
});
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
sharp(input)
|
const output = await sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
brightness: 0.5,
|
brightness: 0.5,
|
||||||
saturation: 0.5,
|
saturation: 0.5,
|
||||||
hue: 90
|
hue: 90,
|
||||||
});
|
})
|
||||||
|
.toBuffer();
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
@@ -474,8 +558,10 @@ Returns **Sharp**
|
|||||||
|
|
||||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||||
|
|
||||||
[8]: /api-channel#removealpha
|
[8]: https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen
|
||||||
|
|
||||||
[9]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
|
[9]: /api-channel#removealpha
|
||||||
|
|
||||||
[10]: https://nodejs.org/api/buffer.html
|
[10]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
|
||||||
|
|
||||||
|
[11]: https://nodejs.org/api/buffer.html
|
||||||
|
|||||||
@@ -188,13 +188,13 @@ Returns **[boolean][10]**
|
|||||||
|
|
||||||
[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation
|
[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation
|
||||||
|
|
||||||
[6]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100
|
[6]: https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100
|
||||||
|
|
||||||
[7]: http://en.wikipedia.org/wiki/Acutance
|
[7]: http://en.wikipedia.org/wiki/Acutance
|
||||||
|
|
||||||
[8]: http://eprints.soton.ac.uk/268086/
|
[8]: http://eprints.soton.ac.uk/268086/
|
||||||
|
|
||||||
[9]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
|
[9]: https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
|
||||||
|
|
||||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,49 @@
|
|||||||
|
|
||||||
Requires libvips v8.12.2
|
Requires libvips v8.12.2
|
||||||
|
|
||||||
|
### v0.30.3 - 14th March 2022
|
||||||
|
|
||||||
|
* Allow `sharpen` options to be provided more consistently as an Object.
|
||||||
|
[#2561](https://github.com/lovell/sharp/issues/2561)
|
||||||
|
|
||||||
|
* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation.
|
||||||
|
[#2935](https://github.com/lovell/sharp/issues/2935)
|
||||||
|
|
||||||
|
* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2).
|
||||||
|
[#3118](https://github.com/lovell/sharp/issues/3118)
|
||||||
|
|
||||||
|
### v0.30.2 - 2nd March 2022
|
||||||
|
|
||||||
|
* Improve performance and accuracy when compositing multiple images.
|
||||||
|
[#2286](https://github.com/lovell/sharp/issues/2286)
|
||||||
|
|
||||||
|
* Expand pkgconfig search path for wider BSD support.
|
||||||
|
[#3106](https://github.com/lovell/sharp/issues/3106)
|
||||||
|
|
||||||
|
* Ensure Windows C++ runtime is linked statically (regression in 0.30.0).
|
||||||
|
[#3110](https://github.com/lovell/sharp/pull/3110)
|
||||||
|
[@kleisauke](https://github.com/kleisauke)
|
||||||
|
|
||||||
|
* Temporarily ignore greyscale ICC profiles to workaround lcms bug.
|
||||||
|
[#3112](https://github.com/lovell/sharp/issues/3112)
|
||||||
|
|
||||||
|
### v0.30.1 - 9th February 2022
|
||||||
|
|
||||||
|
* Allow use of `toBuffer` and `toFile` on the same instance.
|
||||||
|
[#3044](https://github.com/lovell/sharp/issues/3044)
|
||||||
|
|
||||||
|
* Skip shrink-on-load for known libjpeg rounding errors.
|
||||||
|
[#3066](https://github.com/lovell/sharp/issues/3066)
|
||||||
|
[@kleisauke](https://github.com/kleisauke)
|
||||||
|
|
||||||
|
* Ensure withoutReduction does not interfere with contain/crop/embed.
|
||||||
|
[#3081](https://github.com/lovell/sharp/pull/3081)
|
||||||
|
[@kleisauke](https://github.com/kleisauke)
|
||||||
|
|
||||||
|
* Ensure affine interpolator is correctly finalised.
|
||||||
|
[#3083](https://github.com/lovell/sharp/pull/3083)
|
||||||
|
[@kleisauke](https://github.com/kleisauke)
|
||||||
|
|
||||||
### v0.30.0 - 1st February 2022
|
### v0.30.0 - 1st February 2022
|
||||||
|
|
||||||
* Add support for GIF output to prebuilt binaries.
|
* Add support for GIF output to prebuilt binaries.
|
||||||
|
|||||||
BIN
docs/image/sharp-logo.png
Normal file
|
After Width: | Height: | Size: 661 B |
@@ -6,19 +6,13 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
|
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
||||||
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
||||||
connect-src 'self' https://www.google-analytics.com;
|
connect-src 'self' https://www.google-analytics.com;
|
||||||
script-src 'self' 'unsafe-inline' 'unsafe-eval'
|
script-src 'self' 'unsafe-inline' 'unsafe-eval'
|
||||||
https://www.google-analytics.com/analytics.js;">
|
https://www.google-analytics.com/analytics.js;">
|
||||||
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
|
<link rel="icon" type="image/svg+xml" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg">
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
|
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.png">
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
|
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://pixel.plumbing/px/120x120/sharp-logo.svg">
|
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://pixel.plumbing/px/114x114/sharp-logo.svg">
|
|
||||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
|
|
||||||
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
|
|
||||||
<link rel="author" href="/humans.txt" type="text/plain">
|
<link rel="author" href="/humans.txt" type="text/plain">
|
||||||
<link rel="dns-prefetch" href="https://pixel.plumbing">
|
|
||||||
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
@@ -124,7 +118,7 @@
|
|||||||
router: { mode: 'history' },
|
router: { mode: 'history' },
|
||||||
logo: '<div style="display:flex;align-items:center">'
|
logo: '<div style="display:flex;align-items:center">'
|
||||||
+ '<strong>sharp</strong> '
|
+ '<strong>sharp</strong> '
|
||||||
+ '<img src="https://pixel.plumbing/px/16x16/sharp-logo.svg" style="padding:8px" alt="#"> '
|
+ '<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" style="padding:8px" width="32" height="32" alt="#"> '
|
||||||
+ '<span style="opacity:0.8;white-space:pre" class="shorten-strapline">High performance </span> '
|
+ '<span style="opacity:0.8;white-space:pre" class="shorten-strapline">High performance </span> '
|
||||||
+ '<span style="opacity:0.8">Node.js image processing</span> '
|
+ '<span style="opacity:0.8">Node.js image processing</span> '
|
||||||
+ '</div>',
|
+ '</div>',
|
||||||
|
|||||||
@@ -213,9 +213,11 @@ SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
|
|||||||
To get the best performance select the largest memory available.
|
To get the best performance select the largest memory available.
|
||||||
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
||||||
|
|
||||||
## Webpack
|
## Bundlers
|
||||||
|
|
||||||
Ensure sharp is added to the
|
### webpack
|
||||||
|
|
||||||
|
Ensure sharp is excluded from bundling via the
|
||||||
[externals](https://webpack.js.org/configuration/externals/)
|
[externals](https://webpack.js.org/configuration/externals/)
|
||||||
configuration.
|
configuration.
|
||||||
|
|
||||||
@@ -225,6 +227,25 @@ externals: {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### esbuild
|
||||||
|
|
||||||
|
Ensure sharp is excluded from bundling via the
|
||||||
|
[external](https://esbuild.github.io/api/#external)
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
```js
|
||||||
|
buildSync({
|
||||||
|
entryPoints: ['app.js'],
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
external: ['sharp'],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
esbuild app.js --bundle --platform=node --external:sharp
|
||||||
|
```
|
||||||
|
|
||||||
## Worker threads
|
## Worker threads
|
||||||
|
|
||||||
The main thread must call `require('sharp')`
|
The main thread must call `require('sharp')`
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ const colourspace = {
|
|||||||
* Tint the image using the provided chroma while preserving the image luminance.
|
* Tint the image using the provided chroma while preserving the image luminance.
|
||||||
* An alpha channel may be present and will be unchanged by the operation.
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .tint({ r: 255, g: 240, b: 16 })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameter
|
* @throws {Error} Invalid parameter
|
||||||
@@ -37,6 +42,10 @@ function tint (rgb) {
|
|||||||
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
|
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
|
||||||
* which will produce an output image containing one color channel.
|
* which will produce an output image containing one color channel.
|
||||||
* An alpha channel may be present, and will be unchanged by the operation.
|
* An alpha channel may be present, and will be unchanged by the operation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).greyscale().toBuffer();
|
||||||
|
*
|
||||||
* @param {Boolean} [greyscale=true]
|
* @param {Boolean} [greyscale=true]
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -56,6 +56,21 @@ const blend = {
|
|||||||
* @since 0.22.0
|
* @since 0.22.0
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
* await sharp(background)
|
||||||
|
* .composite([
|
||||||
|
* { input: layer1, gravity: 'northwest' },
|
||||||
|
* { input: layer2, gravity: 'southeast' },
|
||||||
|
* ])
|
||||||
|
* .toFile('combined.png');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp('input.gif', { animated: true })
|
||||||
|
* .composite([
|
||||||
|
* { input: 'overlay.png', tile: true, blend: 'saturate' }
|
||||||
|
* ])
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
* sharp('input.png')
|
* sharp('input.png')
|
||||||
* .rotate(180)
|
* .rotate(180)
|
||||||
* .resize(300)
|
* .resize(300)
|
||||||
@@ -89,6 +104,7 @@ const blend = {
|
|||||||
* @param {Number} [images[].raw.width]
|
* @param {Number} [images[].raw.width]
|
||||||
* @param {Number} [images[].raw.height]
|
* @param {Number} [images[].raw.height]
|
||||||
* @param {Number} [images[].raw.channels]
|
* @param {Number} [images[].raw.channels]
|
||||||
|
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
|
||||||
* @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
|
* @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||||
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
|
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
@@ -162,7 +178,6 @@ function composite (images) {
|
|||||||
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
|
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return composite;
|
return composite;
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -186,8 +186,11 @@ const Sharp = function (input, options) {
|
|||||||
medianSize: 0,
|
medianSize: 0,
|
||||||
blurSigma: 0,
|
blurSigma: 0,
|
||||||
sharpenSigma: 0,
|
sharpenSigma: 0,
|
||||||
sharpenFlat: 1,
|
sharpenM1: 1,
|
||||||
sharpenJagged: 2,
|
sharpenM2: 2,
|
||||||
|
sharpenX1: 2,
|
||||||
|
sharpenY2: 10,
|
||||||
|
sharpenY3: 20,
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
thresholdGrayscale: true,
|
thresholdGrayscale: true,
|
||||||
trimThreshold: 0,
|
trimThreshold: 0,
|
||||||
|
|||||||
21
lib/input.js
@@ -299,8 +299,8 @@ function _isStreamInput () {
|
|||||||
*
|
*
|
||||||
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
* - `size`: Total size of image in bytes, for Stream and Buffer input only
|
* - `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
|
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
|
||||||
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
|
||||||
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
|
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
|
||||||
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||||
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
|
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
|
||||||
@@ -343,6 +343,17 @@ function _isStreamInput () {
|
|||||||
* // data contains a WebP image half the width and height of the original JPEG
|
* // data contains a WebP image half the width and height of the original JPEG
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* // Based on EXIF rotation metadata, get the right-side-up width and height:
|
||||||
|
*
|
||||||
|
* const size = getNormalSize(await sharp(input).metadata());
|
||||||
|
*
|
||||||
|
* function getNormalSize({ width, height, orientation }) {
|
||||||
|
* return orientation || 0 >= 5
|
||||||
|
* ? { width: height, height: width }
|
||||||
|
* : { width, height };
|
||||||
|
* }
|
||||||
|
*
|
||||||
* @param {Function} [callback] - called with the arguments `(err, metadata)`
|
* @param {Function} [callback] - called with the arguments `(err, metadata)`
|
||||||
* @returns {Promise<Object>|Sharp}
|
* @returns {Promise<Object>|Sharp}
|
||||||
*/
|
*/
|
||||||
@@ -401,9 +412,9 @@ function metadata (callback) {
|
|||||||
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
||||||
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
|
||||||
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
|
||||||
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
|
||||||
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
|
||||||
*
|
*
|
||||||
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
|
||||||
* written to a buffer in order to run `stats` on the result (see third example).
|
* written to a buffer in order to run `stats` on the result (see third example).
|
||||||
|
|||||||
@@ -86,9 +86,14 @@ const removeVendoredLibvips = function () {
|
|||||||
const pkgConfigPath = function () {
|
const pkgConfigPath = function () {
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
|
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
|
||||||
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
|
return [
|
||||||
.filter(function (p) { return !!p; })
|
brewPkgConfigPath.trim(),
|
||||||
.join(':');
|
env.PKG_CONFIG_PATH,
|
||||||
|
'/usr/local/lib/pkgconfig',
|
||||||
|
'/usr/lib/pkgconfig',
|
||||||
|
'/usr/local/libdata/pkgconfig',
|
||||||
|
'/usr/libdata/pkgconfig'
|
||||||
|
].filter(Boolean).join(':');
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
164
lib/operation.js
@@ -63,6 +63,10 @@ function rotate (angle, options) {
|
|||||||
/**
|
/**
|
||||||
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||||
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).flip().toBuffer();
|
||||||
|
*
|
||||||
* @param {Boolean} [flip=true]
|
* @param {Boolean} [flip=true]
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
@@ -74,6 +78,10 @@ function flip (flip) {
|
|||||||
/**
|
/**
|
||||||
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
||||||
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).flop().toBuffer();
|
||||||
|
*
|
||||||
* @param {Boolean} [flop=true]
|
* @param {Boolean} [flop=true]
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
@@ -185,40 +193,107 @@ function affine (matrix, options) {
|
|||||||
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
||||||
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
||||||
*
|
*
|
||||||
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
|
||||||
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
|
*
|
||||||
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
|
* @example
|
||||||
|
* const data = await sharp(input).sharpen().toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .sharpen({
|
||||||
|
* sigma: 2,
|
||||||
|
* m1: 0
|
||||||
|
* m2: 3,
|
||||||
|
* x1: 3,
|
||||||
|
* y2: 15,
|
||||||
|
* y3: 15,
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
|
||||||
|
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||||
|
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
|
||||||
|
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
|
||||||
|
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
|
||||||
|
* @param {number} [options.y2=10.0] - maximum amount of brightening.
|
||||||
|
* @param {number} [options.y3=20.0] - maximum amount of darkening.
|
||||||
|
* @param {number} [flat] - (deprecated) see `options.m1`.
|
||||||
|
* @param {number} [jagged] - (deprecated) see `options.m2`.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function sharpen (sigma, flat, jagged) {
|
function sharpen (options, flat, jagged) {
|
||||||
if (!is.defined(sigma)) {
|
if (!is.defined(options)) {
|
||||||
// No arguments: default to mild sharpen
|
// No arguments: default to mild sharpen
|
||||||
this.options.sharpenSigma = -1;
|
this.options.sharpenSigma = -1;
|
||||||
} else if (is.bool(sigma)) {
|
} else if (is.bool(options)) {
|
||||||
// Boolean argument: apply mild sharpen?
|
// Deprecated boolean argument: apply mild sharpen?
|
||||||
this.options.sharpenSigma = sigma ? -1 : 0;
|
this.options.sharpenSigma = options ? -1 : 0;
|
||||||
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
|
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
|
||||||
// Numeric argument: specific sigma
|
// Deprecated numeric argument: specific sigma
|
||||||
this.options.sharpenSigma = sigma;
|
this.options.sharpenSigma = options;
|
||||||
// Control over flat areas
|
// Deprecated control over flat areas
|
||||||
if (is.defined(flat)) {
|
if (is.defined(flat)) {
|
||||||
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
||||||
this.options.sharpenFlat = flat;
|
this.options.sharpenM1 = flat;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Control over jagged areas
|
// Deprecated control over jagged areas
|
||||||
if (is.defined(jagged)) {
|
if (is.defined(jagged)) {
|
||||||
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
||||||
this.options.sharpenJagged = jagged;
|
this.options.sharpenM2 = jagged;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (is.plainObject(options)) {
|
||||||
|
if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
|
||||||
|
this.options.sharpenSigma = options.sigma;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
|
throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
|
||||||
|
}
|
||||||
|
if (is.defined(options.m1)) {
|
||||||
|
if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
|
||||||
|
this.options.sharpenM1 = options.m1;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.m2)) {
|
||||||
|
if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
|
||||||
|
this.options.sharpenM2 = options.m2;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.x1)) {
|
||||||
|
if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
|
||||||
|
this.options.sharpenX1 = options.x1;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.y2)) {
|
||||||
|
if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
|
||||||
|
this.options.sharpenY2 = options.y2;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.y3)) {
|
||||||
|
if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
|
||||||
|
this.options.sharpenY3 = options.y3;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -226,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
|
|||||||
/**
|
/**
|
||||||
* Apply median filter.
|
* Apply median filter.
|
||||||
* When used without parameters the default window is 3x3.
|
* When used without parameters the default window is 3x3.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).median().toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).median(5).toBuffer();
|
||||||
|
*
|
||||||
* @param {number} [size=3] square mask size: size x size
|
* @param {number} [size=3] square mask size: size x size
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
@@ -356,6 +438,10 @@ function negate (options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
|
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).normalise().toBuffer();
|
||||||
|
*
|
||||||
* @param {Boolean} [normalise=true]
|
* @param {Boolean} [normalise=true]
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
@@ -366,6 +452,10 @@ function normalise (normalise) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative spelling of normalise.
|
* Alternative spelling of normalise.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input).normalize().toBuffer();
|
||||||
|
*
|
||||||
* @param {Boolean} [normalize=true]
|
* @param {Boolean} [normalize=true]
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
@@ -381,6 +471,14 @@ function normalize (normalize) {
|
|||||||
*
|
*
|
||||||
* @since 0.28.3
|
* @since 0.28.3
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* const output = await sharp(input)
|
||||||
|
* .clahe({
|
||||||
|
* width: 3,
|
||||||
|
* height: 3,
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {number} options.width - integer width of the region in pixels.
|
* @param {number} options.width - integer width of the region in pixels.
|
||||||
* @param {number} options.height - integer height of the region in pixels.
|
* @param {number} options.height - integer height of the region in pixels.
|
||||||
@@ -590,28 +688,38 @@ function recomb (inputMatrix) {
|
|||||||
* @since 0.22.1
|
* @since 0.22.1
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* sharp(input)
|
* // increase brightness by a factor of 2
|
||||||
|
* const output = await sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* brightness: 2 // increase brightness by a factor of 2
|
* brightness: 2
|
||||||
* });
|
* })
|
||||||
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* sharp(input)
|
* @example
|
||||||
|
* // hue-rotate by 180 degrees
|
||||||
|
* const output = await sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* hue: 180 // hue-rotate by 180 degrees
|
* hue: 180
|
||||||
* });
|
* })
|
||||||
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* sharp(input)
|
* @example
|
||||||
|
* // increase lightness by +50
|
||||||
|
* const output = await sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* lightness: 50 // increase lightness by +50
|
* lightness: 50
|
||||||
* });
|
* })
|
||||||
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
* sharp(input)
|
* const output = await sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* brightness: 0.5,
|
* brightness: 0.5,
|
||||||
* saturation: 0.5,
|
* saturation: 0.5,
|
||||||
* hue: 90
|
* hue: 90,
|
||||||
* });
|
* })
|
||||||
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {number} [options.brightness] Brightness multiplier
|
* @param {number} [options.brightness] Brightness multiplier
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ function toBuffer (options, callback) {
|
|||||||
} else if (this.options.resolveWithObject) {
|
} else if (this.options.resolveWithObject) {
|
||||||
this.options.resolveWithObject = false;
|
this.options.resolveWithObject = false;
|
||||||
}
|
}
|
||||||
|
this.options.fileOut = '';
|
||||||
return this._pipeline(is.fn(options) ? options : callback);
|
return this._pipeline(is.fn(options) ? options : callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ try {
|
|||||||
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
|
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
|
||||||
);
|
);
|
||||||
// Check loaded
|
// Check loaded
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32' || /symbol/.test(err.message)) {
|
||||||
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
|
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
|
||||||
if (loadedModule) {
|
if (loadedModule) {
|
||||||
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
|
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ const interpolators = {
|
|||||||
bilinear: 'bilinear',
|
bilinear: 'bilinear',
|
||||||
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
|
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
|
||||||
bicubic: 'bicubic',
|
bicubic: 'bicubic',
|
||||||
/** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
|
/** [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
|
||||||
locallyBoundedBicubic: 'lbb',
|
locallyBoundedBicubic: 'lbb',
|
||||||
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
|
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
|
||||||
nohalo: 'nohalo',
|
nohalo: 'nohalo',
|
||||||
/** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
/** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
||||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
||||||
"version": "0.30.0",
|
"version": "0.30.3",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://github.com/lovell/sharp",
|
"homepage": "https://github.com/lovell/sharp",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -126,8 +126,8 @@
|
|||||||
"vips"
|
"vips"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^4.2.0",
|
"color": "^4.2.1",
|
||||||
"detect-libc": "^2.0.0",
|
"detect-libc": "^2.0.1",
|
||||||
"node-addon-api": "^4.3.0",
|
"node-addon-api": "^4.3.0",
|
||||||
"prebuild-install": "^7.0.1",
|
"prebuild-install": "^7.0.1",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
@@ -143,10 +143,10 @@
|
|||||||
"exif-reader": "^1.0.3",
|
"exif-reader": "^1.0.3",
|
||||||
"icc": "^2.0.0",
|
"icc": "^2.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^9.2.0",
|
"mocha": "^9.2.2",
|
||||||
"mock-fs": "^5.1.2",
|
"mock-fs": "^5.1.2",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"prebuild": "^11.0.2",
|
"prebuild": "^11.0.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semistandard": "^16.0.1"
|
"semistandard": "^16.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -398,6 +398,10 @@ namespace sharp {
|
|||||||
// From filesystem
|
// From filesystem
|
||||||
imageType = DetermineImageType(descriptor->file.data());
|
imageType = DetermineImageType(descriptor->file.data());
|
||||||
if (imageType == ImageType::MISSING) {
|
if (imageType == ImageType::MISSING) {
|
||||||
|
if (descriptor->file.find("<svg") != std::string::npos) {
|
||||||
|
throw vips::VError("Input file is missing, did you mean "
|
||||||
|
"sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
|
||||||
|
}
|
||||||
throw vips::VError("Input file is missing");
|
throw vips::VError("Input file is missing");
|
||||||
}
|
}
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
@@ -885,7 +889,7 @@ namespace sharp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
||||||
Canvas canvas, bool swap, bool withoutEnlargement) {
|
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
|
||||||
if (swap) {
|
if (swap) {
|
||||||
// Swap input width and height when requested.
|
// Swap input width and height when requested.
|
||||||
std::swap(width, height);
|
std::swap(width, height);
|
||||||
@@ -940,9 +944,14 @@ namespace sharp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should not enlarge (oversample) the output image,
|
// We should not reduce or enlarge the output image, if
|
||||||
// if withoutEnlargement is specified.
|
// withoutReduction or withoutEnlargement is specified.
|
||||||
if (withoutEnlargement) {
|
if (withoutReduction) {
|
||||||
|
// Equivalent of VIPS_SIZE_UP
|
||||||
|
hshrink = std::min(1.0, hshrink);
|
||||||
|
vshrink = std::min(1.0, vshrink);
|
||||||
|
} else if (withoutEnlargement) {
|
||||||
|
// Equivalent of VIPS_SIZE_DOWN
|
||||||
hshrink = std::max(1.0, hshrink);
|
hshrink = std::max(1.0, hshrink);
|
||||||
vshrink = std::max(1.0, vshrink);
|
vshrink = std::max(1.0, vshrink);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ namespace sharp {
|
|||||||
the required thumbnail width/height and canvas mode.
|
the required thumbnail width/height and canvas mode.
|
||||||
*/
|
*/
|
||||||
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
||||||
Canvas canvas, bool swap, bool withoutEnlargement);
|
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,8 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||||
*/
|
*/
|
||||||
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
|
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
|
||||||
|
double const x1, double const y2, double const y3) {
|
||||||
if (sigma == -1.0) {
|
if (sigma == -1.0) {
|
||||||
// Fast, mild sharpen
|
// Fast, mild sharpen
|
||||||
VImage sharpen = VImage::new_matrixv(3, 3,
|
VImage sharpen = VImage::new_matrixv(3, 3,
|
||||||
@@ -224,8 +225,14 @@ namespace sharp {
|
|||||||
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
|
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
|
||||||
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
|
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
|
||||||
}
|
}
|
||||||
return image.sharpen(
|
return image
|
||||||
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
|
.sharpen(VImage::option()
|
||||||
|
->set("sigma", sigma)
|
||||||
|
->set("m1", m1)
|
||||||
|
->set("m2", m2)
|
||||||
|
->set("x1", x1)
|
||||||
|
->set("y2", y2)
|
||||||
|
->set("y3", y3))
|
||||||
.colourspace(colourspaceBeforeSharpen);
|
.colourspace(colourspaceBeforeSharpen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||||
*/
|
*/
|
||||||
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
|
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
|
||||||
|
double const x1, double const y2, double const y3);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Threshold an image
|
Threshold an image
|
||||||
|
|||||||
@@ -138,17 +138,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
pageHeight = inputHeight;
|
pageHeight = inputHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If withoutReduction is specified,
|
|
||||||
// Override target width and height if less than respective value from input file
|
|
||||||
if (baton->withoutReduction) {
|
|
||||||
if (baton->width < inputWidth) {
|
|
||||||
baton->width = inputWidth;
|
|
||||||
}
|
|
||||||
if (baton->height < inputHeight) {
|
|
||||||
baton->height = inputHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scaling calculations
|
// Scaling calculations
|
||||||
double hshrink;
|
double hshrink;
|
||||||
double vshrink;
|
double vshrink;
|
||||||
@@ -161,7 +150,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Shrink to pageHeight, so we work for multi-page images
|
// Shrink to pageHeight, so we work for multi-page images
|
||||||
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
||||||
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
||||||
baton->canvas, swap, baton->withoutEnlargement);
|
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
||||||
|
|
||||||
// The jpeg preload shrink.
|
// The jpeg preload shrink.
|
||||||
int jpegShrinkOnLoad = 1;
|
int jpegShrinkOnLoad = 1;
|
||||||
@@ -184,7 +173,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
if (inputImageType == sharp::ImageType::JPEG) {
|
if (inputImageType == sharp::ImageType::JPEG) {
|
||||||
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
|
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
|
||||||
// for more consistent results and avoid occasional small image shifting
|
// for more consistent results and to avoid extra sharpness to the image
|
||||||
int factor = baton->fastShrinkOnLoad ? 1 : 2;
|
int factor = baton->fastShrinkOnLoad ? 1 : 2;
|
||||||
if (shrink >= 8 * factor) {
|
if (shrink >= 8 * factor) {
|
||||||
jpegShrinkOnLoad = 8;
|
jpegShrinkOnLoad = 8;
|
||||||
@@ -193,6 +182,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else if (shrink >= 2 * factor) {
|
} else if (shrink >= 2 * factor) {
|
||||||
jpegShrinkOnLoad = 2;
|
jpegShrinkOnLoad = 2;
|
||||||
}
|
}
|
||||||
|
// Lower shrink-on-load for known libjpeg rounding errors
|
||||||
|
if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
|
||||||
|
jpegShrinkOnLoad /= 2;
|
||||||
|
}
|
||||||
} else if (inputImageType == sharp::ImageType::WEBP ||
|
} else if (inputImageType == sharp::ImageType::WEBP ||
|
||||||
inputImageType == sharp::ImageType::SVG ||
|
inputImageType == sharp::ImageType::SVG ||
|
||||||
inputImageType == sharp::ImageType::PDF) {
|
inputImageType == sharp::ImageType::PDF) {
|
||||||
@@ -270,35 +263,28 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Any pre-shrinking may already have been done
|
// Any pre-shrinking may already have been done
|
||||||
int thumbWidth = image.width();
|
inputWidth = image.width();
|
||||||
int thumbHeight = image.height();
|
inputHeight = image.height();
|
||||||
|
|
||||||
// After pre-shrink, but before the main shrink stage
|
// After pre-shrink, but before the main shrink stage
|
||||||
// Reuse the initial pageHeight if we didn't pre-shrink
|
// Reuse the initial pageHeight if we didn't pre-shrink
|
||||||
int preshrunkPageHeight = shouldPreShrink ? sharp::GetPageHeight(image) : pageHeight;
|
if (shouldPreShrink) {
|
||||||
|
pageHeight = sharp::GetPageHeight(image);
|
||||||
if (baton->fastShrinkOnLoad && jpegShrinkOnLoad > 1) {
|
|
||||||
// JPEG shrink-on-load rounds the output dimensions down, which
|
|
||||||
// may cause incorrect dimensions when fastShrinkOnLoad is enabled
|
|
||||||
// Just recalculate vshrink / hshrink on the main image instead of
|
|
||||||
// the pre-shrunk image when this is the case
|
|
||||||
hshrink = static_cast<double>(thumbWidth) / (static_cast<double>(inputWidth) / hshrink);
|
|
||||||
vshrink = static_cast<double>(preshrunkPageHeight) / (static_cast<double>(pageHeight) / vshrink);
|
|
||||||
} else {
|
|
||||||
// Shrink to preshrunkPageHeight, so we work for multi-page images
|
|
||||||
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
||||||
thumbWidth, preshrunkPageHeight, targetResizeWidth, targetResizeHeight,
|
|
||||||
baton->canvas, swap, baton->withoutEnlargement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int targetHeight = static_cast<int>(std::rint(static_cast<double>(preshrunkPageHeight) / vshrink));
|
// Shrink to pageHeight, so we work for multi-page images
|
||||||
|
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
||||||
|
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
|
||||||
|
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
|
||||||
|
|
||||||
|
int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
|
||||||
int targetPageHeight = targetHeight;
|
int targetPageHeight = targetHeight;
|
||||||
|
|
||||||
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
||||||
// preshrunkPageHeight or we'll have pixels straddling pixel boundaries
|
// pageHeight or we'll have pixels straddling pixel boundaries
|
||||||
if (thumbHeight > preshrunkPageHeight) {
|
if (inputHeight > pageHeight) {
|
||||||
targetHeight *= nPages;
|
targetHeight *= nPages;
|
||||||
vshrink = static_cast<double>(thumbHeight) / targetHeight;
|
vshrink = static_cast<double>(inputHeight) / targetHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we're using a device-independent colour space
|
// Ensure we're using a device-independent colour space
|
||||||
@@ -306,7 +292,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (
|
if (
|
||||||
sharp::HasProfile(image) &&
|
sharp::HasProfile(image) &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
||||||
|
image.interpretation() != VIPS_INTERPRETATION_B_W
|
||||||
) {
|
) {
|
||||||
// Convert to sRGB/P3 using embedded profile
|
// Convert to sRGB/P3 using embedded profile
|
||||||
try {
|
try {
|
||||||
@@ -367,7 +354,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
|
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
|
||||||
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
|
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
|
||||||
|
|
||||||
// Premultiply image alpha channel before all transformations to avoid
|
// Premultiply image alpha channel before all transformations to avoid
|
||||||
// dark fringing around bright pixels
|
// dark fringing around bright pixels
|
||||||
@@ -532,12 +519,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
MultiPageUnsupported(nPages, "Affine");
|
MultiPageUnsupported(nPages, "Affine");
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
||||||
|
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
|
||||||
|
const_cast<char*>(baton->affineInterpolator.data()));
|
||||||
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
||||||
->set("idx", baton->affineIdx)
|
->set("idx", baton->affineIdx)
|
||||||
->set("idy", baton->affineIdy)
|
->set("idy", baton->affineIdy)
|
||||||
->set("odx", baton->affineOdx)
|
->set("odx", baton->affineOdx)
|
||||||
->set("ody", baton->affineOdy)
|
->set("ody", baton->affineOdy)
|
||||||
->set("interpolate", baton->affineInterpolator));
|
->set("interpolate", interp));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend edges
|
// Extend edges
|
||||||
@@ -588,11 +577,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
// Sharpen
|
// Sharpen
|
||||||
if (shouldSharpen) {
|
if (shouldSharpen) {
|
||||||
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
|
||||||
|
baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composite
|
// Composite
|
||||||
if (shouldComposite) {
|
if (shouldComposite) {
|
||||||
|
std::vector<VImage> images = { image };
|
||||||
|
std::vector<int> modes, xs, ys;
|
||||||
for (Composite *composite : baton->composite) {
|
for (Composite *composite : baton->composite) {
|
||||||
VImage compositeImage;
|
VImage compositeImage;
|
||||||
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
||||||
@@ -638,12 +630,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// gravity was used for extract_area, set it back to its default value of 0
|
// gravity was used for extract_area, set it back to its default value of 0
|
||||||
composite->gravity = 0;
|
composite->gravity = 0;
|
||||||
}
|
}
|
||||||
// Ensure image to composite is sRGB with premultiplied alpha
|
// Ensure image to composite is sRGB with unpremultiplied alpha
|
||||||
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
if (!sharp::HasAlpha(compositeImage)) {
|
if (!sharp::HasAlpha(compositeImage)) {
|
||||||
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
||||||
}
|
}
|
||||||
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
|
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
||||||
// Calculate position
|
// Calculate position
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
@@ -661,12 +653,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||||
compositeImage.width(), compositeImage.height(), composite->gravity);
|
compositeImage.width(), compositeImage.height(), composite->gravity);
|
||||||
}
|
}
|
||||||
// Composite
|
images.push_back(compositeImage);
|
||||||
image = image.composite2(compositeImage, composite->mode, VImage::option()
|
modes.push_back(composite->mode);
|
||||||
->set("premultiplied", TRUE)
|
xs.push_back(left);
|
||||||
->set("x", left)
|
ys.push_back(top);
|
||||||
->set("y", top));
|
|
||||||
}
|
}
|
||||||
|
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse premultiplication after all transformations:
|
// Reverse premultiplication after all transformations:
|
||||||
@@ -1409,8 +1401,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
||||||
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
||||||
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
||||||
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
|
||||||
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
|
baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
|
||||||
|
baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
|
||||||
|
baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
|
||||||
|
baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
|
||||||
baton->threshold = sharp::AttrAsInt32(options, "threshold");
|
baton->threshold = sharp::AttrAsInt32(options, "threshold");
|
||||||
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
|
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
|
||||||
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
||||||
@@ -1442,7 +1437,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
||||||
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
||||||
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
||||||
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
|
baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
|
||||||
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
||||||
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
||||||
if (options.Has("boolean")) {
|
if (options.Has("boolean")) {
|
||||||
|
|||||||
@@ -90,8 +90,11 @@ struct PipelineBaton {
|
|||||||
double lightness;
|
double lightness;
|
||||||
int medianSize;
|
int medianSize;
|
||||||
double sharpenSigma;
|
double sharpenSigma;
|
||||||
double sharpenFlat;
|
double sharpenM1;
|
||||||
double sharpenJagged;
|
double sharpenM2;
|
||||||
|
double sharpenX1;
|
||||||
|
double sharpenY2;
|
||||||
|
double sharpenY3;
|
||||||
int threshold;
|
int threshold;
|
||||||
bool thresholdGrayscale;
|
bool thresholdGrayscale;
|
||||||
double trimThreshold;
|
double trimThreshold;
|
||||||
@@ -126,7 +129,7 @@ struct PipelineBaton {
|
|||||||
double affineIdy;
|
double affineIdy;
|
||||||
double affineOdx;
|
double affineOdx;
|
||||||
double affineOdy;
|
double affineOdy;
|
||||||
vips::VInterpolate affineInterpolator;
|
std::string affineInterpolator;
|
||||||
int jpegQuality;
|
int jpegQuality;
|
||||||
bool jpegProgressive;
|
bool jpegProgressive;
|
||||||
std::string jpegChromaSubsampling;
|
std::string jpegChromaSubsampling;
|
||||||
@@ -234,8 +237,11 @@ struct PipelineBaton {
|
|||||||
lightness(0),
|
lightness(0),
|
||||||
medianSize(0),
|
medianSize(0),
|
||||||
sharpenSigma(0.0),
|
sharpenSigma(0.0),
|
||||||
sharpenFlat(1.0),
|
sharpenM1(1.0),
|
||||||
sharpenJagged(2.0),
|
sharpenM2(2.0),
|
||||||
|
sharpenX1(2.0),
|
||||||
|
sharpenY2(10.0),
|
||||||
|
sharpenY3(20.0),
|
||||||
threshold(0),
|
threshold(0),
|
||||||
thresholdGrayscale(true),
|
thresholdGrayscale(true),
|
||||||
trimThreshold(0.0),
|
trimThreshold(0.0),
|
||||||
@@ -268,7 +274,7 @@ struct PipelineBaton {
|
|||||||
affineIdy(0),
|
affineIdy(0),
|
||||||
affineOdx(0),
|
affineOdx(0),
|
||||||
affineOdy(0),
|
affineOdy(0),
|
||||||
affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
|
affineInterpolator("bicubic"),
|
||||||
jpegQuality(80),
|
jpegQuality(80),
|
||||||
jpegProgressive(false),
|
jpegProgressive(false),
|
||||||
jpegChromaSubsampling("4:2:0"),
|
jpegChromaSubsampling("4:2:0"),
|
||||||
|
|||||||
16
src/sharp.cc
@@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
#include <cstdlib>
|
|
||||||
#include <vips/vips8>
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -22,14 +21,6 @@
|
|||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
|
|
||||||
static void empty_invalid_parameter_handler(const wchar_t* expression,
|
|
||||||
const wchar_t* function, const wchar_t* file, unsigned int line,
|
|
||||||
uintptr_t reserved) {
|
|
||||||
// No-op.
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void* sharp_vips_init(void*) {
|
static void* sharp_vips_init(void*) {
|
||||||
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
||||||
vips_init("sharp");
|
vips_init("sharp");
|
||||||
@@ -43,13 +34,6 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|||||||
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
||||||
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
|
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
|
||||||
|
|
||||||
// Tell the CRT to not exit the application when an invalid parameter is
|
|
||||||
// passed. The main issue is that invalid FDs will trigger this behaviour.
|
|
||||||
// See: https://github.com/libvips/libvips/pull/2571.
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
|
|
||||||
_set_invalid_parameter_handler(empty_invalid_parameter_handler);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Methods available to JavaScript
|
// Methods available to JavaScript
|
||||||
exports.Set("metadata", Napi::Function::New(env, metadata));
|
exports.Set("metadata", Napi::Function::New(env, metadata));
|
||||||
exports.Set("pipeline", Napi::Function::New(env, pipeline));
|
exports.Set("pipeline", Napi::Function::New(env, pipeline));
|
||||||
|
|||||||
BIN
test/fixtures/expected/composite-multiple.png
vendored
|
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 320 B |
BIN
test/fixtures/expected/composite.blend.dest-over.png
vendored
|
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 292 B |
BIN
test/fixtures/expected/composite.blend.over.png
vendored
|
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 292 B |
BIN
test/fixtures/expected/composite.blend.saturate.png
vendored
|
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 286 B |
BIN
test/fixtures/expected/composite.blend.xor.png
vendored
|
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 285 B |
BIN
test/fixtures/expected/fast-shrink-on-load-false.png
vendored
|
Before Width: | Height: | Size: 270 B |
BIN
test/fixtures/expected/fast-shrink-on-load-true.png
vendored
|
Before Width: | Height: | Size: 265 B |
BIN
test/fixtures/expected/fast-shrink-on-load.png
vendored
Normal file
|
After Width: | Height: | Size: 277 B |
BIN
test/fixtures/expected/icc-cmyk.jpg
vendored
|
Before Width: | Height: | Size: 943 KiB After Width: | Height: | Size: 943 KiB |
BIN
test/fixtures/expected/overlay-gravity-center.jpg
vendored
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
test/fixtures/expected/overlay-gravity-centre.jpg
vendored
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
test/fixtures/expected/overlay-gravity-east.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
test/fixtures/expected/overlay-gravity-north.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
test/fixtures/expected/overlay-gravity-northeast.jpg
vendored
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
test/fixtures/expected/overlay-gravity-northwest.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
test/fixtures/expected/overlay-gravity-south.jpg
vendored
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
test/fixtures/expected/overlay-gravity-southeast.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
test/fixtures/expected/overlay-gravity-southwest.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
test/fixtures/expected/overlay-gravity-west.jpg
vendored
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -45,22 +45,20 @@ const blends = [
|
|||||||
|
|
||||||
// Test
|
// Test
|
||||||
describe('composite', () => {
|
describe('composite', () => {
|
||||||
it('blend', () => Promise.all(
|
blends.forEach(blend => {
|
||||||
blends.map(blend => {
|
it(`blend ${blend}`, async () => {
|
||||||
const filename = `composite.blend.${blend}.png`;
|
const filename = `composite.blend.${blend}.png`;
|
||||||
const actual = fixtures.path(`output.${filename}`);
|
const actual = fixtures.path(`output.${filename}`);
|
||||||
const expected = fixtures.expected(filename);
|
const expected = fixtures.expected(filename);
|
||||||
return sharp(redRect)
|
await sharp(redRect)
|
||||||
.composite([{
|
.composite([{
|
||||||
input: blueRect,
|
input: blueRect,
|
||||||
blend
|
blend
|
||||||
}])
|
}])
|
||||||
.toFile(actual)
|
.toFile(actual);
|
||||||
.then(() => {
|
|
||||||
fixtures.assertMaxColourDistance(actual, expected);
|
fixtures.assertMaxColourDistance(actual, expected);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
));
|
|
||||||
|
|
||||||
it('premultiplied true', () => {
|
it('premultiplied true', () => {
|
||||||
const filename = 'composite.premultiplied.png';
|
const filename = 'composite.premultiplied.png';
|
||||||
@@ -121,11 +119,11 @@ describe('composite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiple', () => {
|
it('multiple', async () => {
|
||||||
const filename = 'composite-multiple.png';
|
const filename = 'composite-multiple.png';
|
||||||
const actual = fixtures.path(`output.${filename}`);
|
const actual = fixtures.path(`output.${filename}`);
|
||||||
const expected = fixtures.expected(filename);
|
const expected = fixtures.expected(filename);
|
||||||
return sharp(redRect)
|
await sharp(redRect)
|
||||||
.composite([{
|
.composite([{
|
||||||
input: blueRect,
|
input: blueRect,
|
||||||
gravity: 'northeast'
|
gravity: 'northeast'
|
||||||
@@ -133,11 +131,9 @@ describe('composite', () => {
|
|||||||
input: greenRect,
|
input: greenRect,
|
||||||
gravity: 'southwest'
|
gravity: 'southwest'
|
||||||
}])
|
}])
|
||||||
.toFile(actual)
|
.toFile(actual);
|
||||||
.then(() => {
|
|
||||||
fixtures.assertMaxColourDistance(actual, expected);
|
fixtures.assertMaxColourDistance(actual, expected);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('zero offset', done => {
|
it('zero offset', done => {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ describe('Extend', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Premultiply background when compositing', async () => {
|
it('Premultiply background when compositing', async () => {
|
||||||
const background = '#bf1942cc';
|
const background = { r: 191, g: 25, b: 66, alpha: 0.8 };
|
||||||
const data = await sharp({
|
const data = await sharp({
|
||||||
create: {
|
create: {
|
||||||
width: 1, height: 1, channels: 4, background: '#fff0'
|
width: 1, height: 1, channels: 4, background: '#fff0'
|
||||||
@@ -158,10 +158,6 @@ describe('Extend', function () {
|
|||||||
})
|
})
|
||||||
.raw()
|
.raw()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
const [r1, g1, b1, a1, r2, g2, b2, a2] = data;
|
assert.deepStrictEqual(Array.from(data), [191, 25, 66, 204, 191, 25, 66, 204]);
|
||||||
assert.strictEqual(true, Math.abs(r2 - r1) < 2);
|
|
||||||
assert.strictEqual(true, Math.abs(g2 - g1) < 2);
|
|
||||||
assert.strictEqual(true, Math.abs(b2 - b1) < 2);
|
|
||||||
assert.strictEqual(true, Math.abs(a2 - a1) < 2);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -328,6 +328,20 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Allow use of toBuffer and toFile with same instance', async () => {
|
||||||
|
const instance = sharp({
|
||||||
|
create: {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
channels: 3,
|
||||||
|
background: 'red'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await instance.toFile(fixtures.path('output.jpg'));
|
||||||
|
const data = await instance.toBuffer();
|
||||||
|
assert.strictEqual(Buffer.isBuffer(data), true);
|
||||||
|
});
|
||||||
|
|
||||||
it('Fail when output File is input File', function (done) {
|
it('Fail when output File is input File', function (done) {
|
||||||
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
|
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
|
||||||
assert(err instanceof Error);
|
assert(err instanceof Error);
|
||||||
|
|||||||
@@ -47,19 +47,12 @@ describe('JP2 output', () => {
|
|||||||
sharp(fixtures.inputJp2)
|
sharp(fixtures.inputJp2)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.toBuffer(function (err, buffer80) {
|
.toBuffer(function (err, buffer80) {
|
||||||
if (err) throw err;
|
|
||||||
sharp(fixtures.inputJp2)
|
|
||||||
.resize(320, 240)
|
|
||||||
.jp2({ quality: 90 })
|
|
||||||
.toBuffer(function (err, buffer90) {
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert(buffer70.length < buffer80.length);
|
assert(buffer70.length < buffer80.length);
|
||||||
assert(buffer80.length < buffer90.length);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('Without chroma subsampling generates larger file', function (done) {
|
it('Without chroma subsampling generates larger file', function (done) {
|
||||||
// First generate with chroma subsampling (default)
|
// First generate with chroma subsampling (default)
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ describe('Image metadata', function () {
|
|||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(64)
|
.resize(64)
|
||||||
.withMetadata({ icc: 'cmyk' })
|
.withMetadata({ icc: 'cmyk' })
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
sharp(output).metadata(function (err, metadata) {
|
sharp(output).metadata(function (err, metadata) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
@@ -543,7 +543,7 @@ describe('Image metadata', function () {
|
|||||||
assert.strictEqual('Relative', profile.intent);
|
assert.strictEqual('Relative', profile.intent);
|
||||||
assert.strictEqual('Printer', profile.deviceClass);
|
assert.strictEqual('Printer', profile.deviceClass);
|
||||||
});
|
});
|
||||||
fixtures.assertSimilar(output, fixtures.expected('icc-cmyk.jpg'), { threshold: 0 }, done);
|
fixtures.assertSimilar(output, fixtures.expected('icc-cmyk.jpg'), { threshold: 1 }, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ describe('Resize dimensions', function () {
|
|||||||
assert.strictEqual(true, data.length > 0);
|
assert.strictEqual(true, data.length > 0);
|
||||||
assert.strictEqual('jpeg', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(2800, info.width);
|
assert.strictEqual(2800, info.width);
|
||||||
assert.strictEqual(2225, info.height);
|
assert.strictEqual(2286, info.height);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -383,46 +383,12 @@ describe('Resize dimensions', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, data.length > 0);
|
assert.strictEqual(true, data.length > 0);
|
||||||
assert.strictEqual('jpeg', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(2725, info.width);
|
assert.strictEqual(2817, info.width);
|
||||||
assert.strictEqual(2300, info.height);
|
assert.strictEqual(2300, info.height);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Do not crop when fit = cover and withoutReduction = true and width >= outputWidth, and height < outputHeight', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize({
|
|
||||||
width: 3000,
|
|
||||||
height: 1000,
|
|
||||||
withoutReduction: true
|
|
||||||
})
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3000, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Do not crop when fit = cover and withoutReduction = true and width < outputWidth, and height >= outputHeight', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize({
|
|
||||||
width: 1500,
|
|
||||||
height: 2226,
|
|
||||||
withoutReduction: true
|
|
||||||
})
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2226, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Do enlarge when input width is less than output width', function (done) {
|
it('Do enlarge when input width is less than output width', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize({
|
.resize({
|
||||||
@@ -637,28 +603,21 @@ describe('Resize dimensions', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fastShrinkOnLoad: false ensures image is not shifted', function (done) {
|
[
|
||||||
return sharp(fixtures.inputJpgCenteredImage)
|
true,
|
||||||
.resize(9, 8, { fastShrinkOnLoad: false })
|
false
|
||||||
|
].forEach(function (value) {
|
||||||
|
it(`fastShrinkOnLoad: ${value} does not causes image shifts`, function (done) {
|
||||||
|
sharp(fixtures.inputJpgCenteredImage)
|
||||||
|
.resize(9, 8, { fastShrinkOnLoad: value })
|
||||||
.png()
|
.png()
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(9, info.width);
|
assert.strictEqual(9, info.width);
|
||||||
assert.strictEqual(8, info.height);
|
assert.strictEqual(8, info.height);
|
||||||
fixtures.assertSimilar(fixtures.expected('fast-shrink-on-load-false.png'), data, done);
|
fixtures.assertSimilar(fixtures.expected('fast-shrink-on-load.png'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fastShrinkOnLoad: true (default) might result in shifted image', function (done) {
|
|
||||||
return sharp(fixtures.inputJpgCenteredImage)
|
|
||||||
.resize(9, 8)
|
|
||||||
.png()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(9, info.width);
|
|
||||||
assert.strictEqual(8, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('fast-shrink-on-load-true.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
@@ -780,6 +739,27 @@ describe('Resize dimensions', function () {
|
|||||||
assert.strictEqual(info.height, 1);
|
assert.strictEqual(info.height, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Skip JPEG shrink-on-load for known libjpeg rounding errors', async () => {
|
||||||
|
const input = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1000,
|
||||||
|
height: 667,
|
||||||
|
channels: 3,
|
||||||
|
background: 'red'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const output = await sharp(input)
|
||||||
|
.resize({ width: 500 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const { width, height } = await sharp(output).metadata();
|
||||||
|
assert.strictEqual(width, 500);
|
||||||
|
assert.strictEqual(height, 334);
|
||||||
|
});
|
||||||
|
|
||||||
it('unknown kernel throws', function () {
|
it('unknown kernel throws', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().resize(null, null, { kernel: 'unknown' });
|
sharp().resize(null, null, { kernel: 'unknown' });
|
||||||
|
|||||||
@@ -45,6 +45,22 @@ describe('Sharpen', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sigma=3.5, m1=2, m2=4', (done) => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen({ sigma: 3.5, m1: 2, m2: 4 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sigma=3.5, m1=2, m2=4, x1=2, y2=5, y3=25', (done) => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen({ sigma: 3.5, m1: 2, m2: 4, x1: 2, y2: 5, y3: 25 })
|
||||||
|
.toBuffer()
|
||||||
|
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
|
||||||
|
});
|
||||||
|
|
||||||
if (!process.env.SHARP_TEST_WITHOUT_CACHE) {
|
if (!process.env.SHARP_TEST_WITHOUT_CACHE) {
|
||||||
it('specific radius/levels with alpha channel', function (done) {
|
it('specific radius/levels with alpha channel', function (done) {
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
@@ -92,6 +108,36 @@ describe('Sharpen', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid options.sigma', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: -1 }),
|
||||||
|
/Expected number between 0\.01 and 10000 for options\.sigma but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
|
it('invalid options.m1', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: 1, m1: -1 }),
|
||||||
|
/Expected number between 0 and 10000 for options\.m1 but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
|
it('invalid options.m2', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: 1, m2: -1 }),
|
||||||
|
/Expected number between 0 and 10000 for options\.m2 but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
|
it('invalid options.x1', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: 1, x1: -1 }),
|
||||||
|
/Expected number between 0 and 10000 for options\.x1 but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
|
it('invalid options.y2', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: 1, y2: -1 }),
|
||||||
|
/Expected number between 0 and 10000 for options\.y2 but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
|
it('invalid options.y3', () => assert.throws(
|
||||||
|
() => sharp().sharpen({ sigma: 1, y3: -1 }),
|
||||||
|
/Expected number between 0 and 10000 for options\.y3 but received -1 of type number/
|
||||||
|
));
|
||||||
|
|
||||||
it('sharpened image is larger than non-sharpened', function (done) {
|
it('sharpened image is larger than non-sharpened', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
|
|||||||
@@ -135,4 +135,11 @@ describe('SVG input', function () {
|
|||||||
assert.strictEqual(info.height, 240);
|
assert.strictEqual(info.height, 240);
|
||||||
assert.strictEqual(info.channels, 4);
|
assert.strictEqual(info.channels, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Detects SVG passed as a string', () =>
|
||||||
|
assert.rejects(
|
||||||
|
() => sharp('<svg></svg>').toBuffer(),
|
||||||
|
/Input file is missing, did you mean/
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,10 +68,13 @@ describe('Utilities', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Counters', function () {
|
describe('Counters', function () {
|
||||||
it('Have zero value at rest', function () {
|
it('Have zero value at rest', (done) => {
|
||||||
|
queueMicrotask(() => {
|
||||||
const counters = sharp.counters();
|
const counters = sharp.counters();
|
||||||
assert.strictEqual(0, counters.queue);
|
assert.strictEqual(0, counters.queue);
|
||||||
assert.strictEqual(0, counters.process);
|
assert.strictEqual(0, counters.process);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||