Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
844deaf480 | ||
|
|
efbb0c22fd | ||
|
|
da0b594900 | ||
|
|
78dada9126 | ||
|
|
15f5cd4671 | ||
|
|
9eb2e94404 | ||
|
|
e40b068628 | ||
|
|
2c46528269 | ||
|
|
584807b4f5 | ||
|
|
a7fa7014ef | ||
|
|
f92e33fbff | ||
|
|
0f1e7ef6f6 | ||
|
|
89e204d824 | ||
|
|
2a71f1830f | ||
|
|
def99a294a | ||
|
|
9d760f3958 | ||
|
|
0265d305fe | ||
|
|
a472aea025 | ||
|
|
01ffa80338 | ||
|
|
789d4851ea | ||
|
|
4490a93430 | ||
|
|
ac0dc10bd5 | ||
|
|
5740f4545e | ||
|
|
a9d692fb43 | ||
|
|
df971207b8 | ||
|
|
3a64a0529a | ||
|
|
76cda885fb | ||
|
|
1a563360c6 | ||
|
|
ca22af203f | ||
|
|
9fa516e849 | ||
|
|
12f472126d | ||
|
|
18be09f1d7 | ||
|
|
b3c3290f90 | ||
|
|
123f95c85a | ||
|
|
5b0fba4c01 | ||
|
|
37f7ccfff4 | ||
|
|
51811d06e2 | ||
|
|
181731f8f4 | ||
|
|
ae79d26ead | ||
|
|
eacb8337fa | ||
|
|
99bf279de8 | ||
|
|
1b0eb6ab53 | ||
|
|
891cf67d0b | ||
|
|
eaf8a86bf2 | ||
|
|
3400976d61 | ||
|
|
2d49f0e93e | ||
|
|
b0c69f1ee9 | ||
|
|
27d0c35a01 | ||
|
|
32a22b5420 | ||
|
|
d1004eed02 | ||
|
|
70e6bb0162 | ||
|
|
32aa3b4b20 | ||
|
|
df24b30755 | ||
|
|
4de74bea94 | ||
|
|
28b87db760 | ||
|
|
fbd4970b57 | ||
|
|
f5da147a58 | ||
|
|
eee0dd36d9 | ||
|
|
f7b29d7b59 | ||
|
|
0b806187fc | ||
|
|
c1393daa70 | ||
|
|
31c1cfb049 | ||
|
|
afc35c2208 | ||
|
|
6eb2add3bf | ||
|
|
5cdb2b83d5 | ||
|
|
1eb66c0944 | ||
|
|
55c4d8807c | ||
|
|
9a54a034e1 | ||
|
|
f5109560d6 | ||
|
|
953a94885b | ||
|
|
0e3bd46ca3 | ||
|
|
4b38f56d02 | ||
|
|
0fe857c5ac | ||
|
|
1bf06bd5b4 | ||
|
|
6e3f4c3c92 | ||
|
|
8583eb1235 | ||
|
|
c3a852eecf | ||
|
|
3a44748f49 | ||
|
|
e1bc8674fd | ||
|
|
a618ce7a15 | ||
|
|
a44168c8c7 | ||
|
|
74e3f73934 | ||
|
|
b9261c243c | ||
|
|
cc9f91f37c | ||
|
|
212a6e7519 | ||
|
|
e547eaa180 | ||
|
|
9a0d9eed74 | ||
|
|
dd56a9699e | ||
|
|
ea7cf2a2ef | ||
|
|
76c4c51e2a | ||
|
|
b46ab510da | ||
|
|
3e327a586c | ||
|
|
974fab946e | ||
|
|
f998a8f249 | ||
|
|
be331e958e | ||
|
|
254944f8ab | ||
|
|
f1e640d231 | ||
|
|
c295f06a6f | ||
|
|
6288c7bced | ||
|
|
d247c02762 | ||
|
|
1b84ccbbe9 | ||
|
|
e4160c684d | ||
|
|
905518fab0 | ||
|
|
8ff33763ce | ||
|
|
cbf741cac7 | ||
|
|
6c2e2be41d | ||
|
|
e0d3c6e05d | ||
|
|
e3cab7f10f | ||
|
|
204463ffbb | ||
|
|
1bcd3700c5 | ||
|
|
c99a11cff5 | ||
|
|
81c74f57e0 | ||
|
|
7a8ab452c5 | ||
|
|
bb91912883 | ||
|
|
afc4c5bf79 | ||
|
|
e40a881ab4 | ||
|
|
c1b13adac3 | ||
|
|
29e09898f7 | ||
|
|
853a20358e | ||
|
|
8bb30d7801 | ||
|
|
a333b87f5d | ||
|
|
4662527a17 | ||
|
|
b10d8f89ca | ||
|
|
f903e1465e | ||
|
|
a75718565c | ||
|
|
4d82331bf6 | ||
|
|
b91875d3d9 | ||
|
|
a0568ec0c3 | ||
|
|
48e3ea5e29 | ||
|
|
93b29057e4 | ||
|
|
db654de385 | ||
|
|
a6aeef612b | ||
|
|
7bf6cbd669 | ||
|
|
04c31b35a7 | ||
|
|
ee9cdb6598 | ||
|
|
8960eb8309 | ||
|
|
54d9dc46f5 | ||
|
|
51b4a7c564 | ||
|
|
5b03579e5c | ||
|
|
58c2af3251 | ||
|
|
ee948ac6fa | ||
|
|
66a3ce5e55 | ||
|
|
75e5afcd42 | ||
|
|
d396a4e65d | ||
|
|
ae1dbcdf4e | ||
|
|
4c29368b51 | ||
|
|
36e55969d2 | ||
|
|
985e881e7a | ||
|
|
0b116715aa | ||
|
|
9deac83322 | ||
|
|
5d36f5f699 | ||
|
|
926572b41e | ||
|
|
d0c8e95641 | ||
|
|
b0ca23c3e7 | ||
|
|
c3a0d5f5d0 | ||
|
|
1d36936954 | ||
|
|
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 |
@@ -3,76 +3,76 @@ version: 2.1
|
||||
workflows:
|
||||
build:
|
||||
jobs:
|
||||
- linux-arm64-glibc-node-12:
|
||||
- linux-arm64-glibc-node-14:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
- linux-arm64-musl-node-12:
|
||||
- linux-arm64-musl-node-14:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
- linux-arm64-glibc-node-16:
|
||||
- linux-arm64-glibc-node-18:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
- linux-arm64-musl-node-16:
|
||||
- linux-arm64-musl-node-18:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
|
||||
jobs:
|
||||
linux-arm64-glibc-node-12:
|
||||
linux-arm64-glibc-node-14:
|
||||
resource_class: arm.medium
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
|
||||
sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
- run: sudo docker exec sharp sh -c "npm test"
|
||||
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 5 --upload=$prebuild_upload\" || true"
|
||||
linux-arm64-glibc-node-16:
|
||||
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 7 --upload=$prebuild_upload\" || true"
|
||||
linux-arm64-glibc-node-18:
|
||||
resource_class: arm.medium
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
|
||||
sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_16.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_18.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
|
||||
sudo docker cp . sharp:/mnt/sharp/.
|
||||
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||
- run: sudo docker exec sharp sh -c "npm test"
|
||||
linux-arm64-musl-node-12:
|
||||
linux-arm64-musl-node-14:
|
||||
resource_class: arm.medium
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12-alpine3.11
|
||||
sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
|
||||
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14-alpine3.12
|
||||
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
|
||||
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||
- run: sudo docker exec sharp sh -c "npm test"
|
||||
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 5 --upload=$prebuild_upload\" || true"
|
||||
linux-arm64-musl-node-16:
|
||||
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 7 --upload=$prebuild_upload\" || true"
|
||||
linux-arm64-musl-node-18:
|
||||
resource_class: arm.medium
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
image: ubuntu-2004:current
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
sudo docker run -dit --name sharp --workdir /mnt/sharp node:16-alpine3.11
|
||||
sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
|
||||
sudo docker run -dit --name sharp --workdir /mnt/sharp node:18-alpine3.14
|
||||
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
|
||||
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
|
||||
sudo docker cp . sharp:/mnt/sharp/.
|
||||
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-0-snap
|
||||
image_family: freebsd-14-0-snap
|
||||
|
||||
task:
|
||||
name: FreeBSD 13.0
|
||||
name: FreeBSD
|
||||
env:
|
||||
IGNORE_OSVERSION: yes
|
||||
skip_notifications: true
|
||||
prerequisites_script:
|
||||
- pkg update -f
|
||||
- pkg upgrade -y
|
||||
- pkg install -y pkgconf vips node npm
|
||||
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
|
||||
install_script:
|
||||
- npm install --build-from-source --unsafe-perm
|
||||
test_script:
|
||||
|
||||
2
.github/CONTRIBUTING.md
vendored
@@ -16,7 +16,7 @@ If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exist
|
||||
it's probably fastest to add a comment to it about your requirement.
|
||||
|
||||
Implementation is usually straightforward if libvips
|
||||
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
|
||||
[already supports](https://www.libvips.org/API/current/func-list.html)
|
||||
the feature you need.
|
||||
|
||||
## Submit a Pull Request to fix a bug
|
||||
|
||||
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. -->
|
||||
|
||||
31
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -7,11 +7,24 @@ labels: installation
|
||||
|
||||
<!-- Please try to answer as many of these questions as possible. -->
|
||||
|
||||
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
|
||||
## Possible install-time or require-time problem
|
||||
|
||||
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?
|
||||
<!-- Please place an [x] in the box to confirm. -->
|
||||
|
||||
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`?
|
||||
- [ ] 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?
|
||||
|
||||
@@ -19,6 +32,14 @@ If you are using npm v7 or later, does the user running `npm install` own the di
|
||||
|
||||
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 --foreground-scripts sharp` in an empty directory?
|
||||
|
||||
What is the output of running `npx envinfo --binaries --system`?
|
||||
<details>
|
||||
|
||||
<!-- Please provide output of `npm install --verbose --foreground-scripts sharp` in an empty directory 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. -->
|
||||
|
||||
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. -->
|
||||
|
||||
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. -->
|
||||
|
||||
22
.github/workflows/ci-darwin-arm64v8.yml
vendored
@@ -2,29 +2,33 @@ name: CI (MacStadium)
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
permissions: {}
|
||||
jobs:
|
||||
CI:
|
||||
permissions:
|
||||
contents: write # for npx prebuild to make release
|
||||
name: Node.js ${{ matrix.nodejs_version }} ${{ matrix.nodejs_arch }} ${{ matrix.prebuild && '- prebuild' }}
|
||||
runs-on: macos-m1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- nodejs_version: 12
|
||||
nodejs_architecture: x64
|
||||
- nodejs_version: 16
|
||||
nodejs_architecture: arm64
|
||||
- nodejs_version: 14
|
||||
nodejs_arch: x64
|
||||
- nodejs_version: 18
|
||||
nodejs_arch: arm64
|
||||
prebuild: true
|
||||
defaults:
|
||||
run:
|
||||
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
|
||||
steps:
|
||||
- name: Dependencies
|
||||
uses: actions/setup-node@v2
|
||||
- name: Dependencies (Node.js)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.nodejs_version }}
|
||||
architecture: ${{ matrix.nodejs_architecture }}
|
||||
architecture: ${{ matrix.nodejs_arch }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Install
|
||||
run: npm install --build-from-source --unsafe-perm
|
||||
- name: Test
|
||||
@@ -33,4 +37,4 @@ jobs:
|
||||
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx prebuild --runtime napi --target 5
|
||||
run: npx prebuild --runtime napi --target 7
|
||||
|
||||
94
.github/workflows/ci.yml
vendored
@@ -2,66 +2,95 @@ name: CI (GitHub)
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
permissions: {}
|
||||
jobs:
|
||||
CI:
|
||||
permissions:
|
||||
contents: write # for npx prebuild to make release
|
||||
name: ${{ matrix.container || matrix.os }} - Node.js ${{ matrix.nodejs_version }} ${{ matrix.nodejs_arch }} ${{ matrix.prebuild && '- prebuild' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
container: centos:7
|
||||
nodejs_version: 12
|
||||
coverage: true
|
||||
prebuild: true
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
container: centos:7
|
||||
nodejs_version: 14
|
||||
- os: ubuntu-20.04
|
||||
prebuild: true
|
||||
- os: ubuntu-22.04
|
||||
container: centos:7
|
||||
nodejs_version: 16
|
||||
- os: ubuntu-20.04
|
||||
container: node:12-alpine3.11
|
||||
- os: ubuntu-22.04
|
||||
container: rockylinux:8
|
||||
nodejs_version: 18
|
||||
- os: ubuntu-22.04
|
||||
container: node:14-alpine3.12
|
||||
prebuild: true
|
||||
- os: ubuntu-20.04
|
||||
container: node:14-alpine3.11
|
||||
- os: ubuntu-20.04
|
||||
container: node:14-alpine3.13
|
||||
- os: ubuntu-20.04
|
||||
container: node:16-alpine3.11
|
||||
- os: macos-10.15
|
||||
nodejs_version: 12
|
||||
prebuild: true
|
||||
- os: macos-10.15
|
||||
- os: ubuntu-22.04
|
||||
container: node:16-alpine3.12
|
||||
- os: ubuntu-22.04
|
||||
container: node:18-alpine3.14
|
||||
- os: macos-11
|
||||
nodejs_version: 14
|
||||
- os: macos-10.15
|
||||
nodejs_version: 16
|
||||
- os: windows-2019
|
||||
nodejs_version: 12
|
||||
prebuild: true
|
||||
nodejs_arch: x64
|
||||
- os: macos-11
|
||||
nodejs_version: 16
|
||||
nodejs_arch: x64
|
||||
- os: macos-11
|
||||
nodejs_version: 18
|
||||
nodejs_arch: x64
|
||||
- os: windows-2019
|
||||
nodejs_version: 14
|
||||
nodejs_arch: x86
|
||||
prebuild: true
|
||||
- os: windows-2019
|
||||
nodejs_version: 16
|
||||
nodejs_arch: x86
|
||||
- os: windows-2019
|
||||
nodejs_version: 18
|
||||
nodejs_arch: x86
|
||||
- os: windows-2019
|
||||
nodejs_version: 14
|
||||
nodejs_arch: x64
|
||||
prebuild: true
|
||||
- os: windows-2019
|
||||
nodejs_version: 16
|
||||
nodejs_arch: x64
|
||||
- os: windows-2019
|
||||
nodejs_version: 18
|
||||
nodejs_arch: x64
|
||||
steps:
|
||||
- name: Dependencies (Linux glibc)
|
||||
if: contains(matrix.container, 'centos')
|
||||
run: |
|
||||
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
|
||||
yum install -y centos-release-scl
|
||||
yum install -y devtoolset-10-gcc-c++ make git python3 nodejs
|
||||
echo "/opt/rh/devtoolset-10/root/usr/bin" >> $GITHUB_PATH
|
||||
yum install -y devtoolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
|
||||
echo "/opt/rh/devtoolset-11/root/usr/bin" >> $GITHUB_PATH
|
||||
- name: Dependencies (Rocky Linux glibc)
|
||||
if: contains(matrix.container, 'rockylinux')
|
||||
run: |
|
||||
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
|
||||
dnf install -y gcc-toolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
|
||||
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
|
||||
- name: Dependencies (Linux musl)
|
||||
if: contains(matrix.container, 'alpine')
|
||||
run: apk add build-base git python3 --update-cache
|
||||
- name: Dependencies (macOS, Windows)
|
||||
run: apk add build-base git python3 font-noto --update-cache
|
||||
- name: Dependencies (Python 3.10 - macOS, Windows)
|
||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Dependencies (Node.js - macOS, Windows)
|
||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.nodejs_version }}
|
||||
architecture: ${{ matrix.nodejs_arch }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Fix working directory ownership
|
||||
if: matrix.container
|
||||
run: chown root.root .
|
||||
@@ -69,13 +98,8 @@ jobs:
|
||||
run: npm install --build-from-source --unsafe-perm
|
||||
- name: Test
|
||||
run: npm test
|
||||
- name: Coverage
|
||||
if: matrix.coverage
|
||||
uses: coverallsapp/github-action@v1.1.2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Prebuild
|
||||
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx prebuild --runtime napi --target 5
|
||||
run: npx prebuild --runtime napi --target 7
|
||||
|
||||
7
.mocharc.jsonc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"parallel": true,
|
||||
"slow": 1000,
|
||||
"timeout": 30000,
|
||||
"require": "./test/beforeEach.js",
|
||||
"spec": "./test/unit/*.js"
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
|
||||
As well as image resizing, operations such as
|
||||
rotation, extraction, compositing and gamma correction are available.
|
||||
|
||||
Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
|
||||
Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
## Documentation
|
||||
@@ -98,7 +98,6 @@ readableStream
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=main)
|
||||
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
||||
|
||||
## 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
|
||||
@@ -15,10 +15,11 @@
|
||||
'_ALLOW_KEYWORD_MACROS'
|
||||
],
|
||||
'sources': [
|
||||
'src/libvips/cplusplus/VError.cpp',
|
||||
'src/libvips/cplusplus/VConnection.cpp',
|
||||
'src/libvips/cplusplus/VError.cpp',
|
||||
'src/libvips/cplusplus/VImage.cpp',
|
||||
'src/libvips/cplusplus/VInterpolate.cpp',
|
||||
'src/libvips/cplusplus/VImage.cpp'
|
||||
'src/libvips/cplusplus/VRegion.cpp'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(sharp_vendor_dir)/include',
|
||||
@@ -39,7 +40,6 @@
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1,
|
||||
'Optimization': 1,
|
||||
'RuntimeLibrary': '2', # /MD
|
||||
'WholeProgramOptimization': 'true'
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
@@ -70,7 +70,7 @@
|
||||
}, {
|
||||
'target_name': 'sharp-<(platform_and_arch)',
|
||||
'defines': [
|
||||
'NAPI_VERSION=5'
|
||||
'NAPI_VERSION=7'
|
||||
],
|
||||
'dependencies': [
|
||||
'<!(node -p "require(\'node-addon-api\').gyp")',
|
||||
@@ -206,7 +206,6 @@
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1,
|
||||
'Optimization': 1,
|
||||
'RuntimeLibrary': '2', # /MD
|
||||
'WholeProgramOptimization': 'true'
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
|
||||
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
|
||||
As well as image resizing, operations such as
|
||||
rotation, extraction, compositing and gamma correction are available.
|
||||
|
||||
Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
|
||||
Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
### Formats
|
||||
@@ -63,6 +63,10 @@ PNG filtering is disabled by default,
|
||||
which for diagrams and line art often produces the same result
|
||||
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
|
||||
|
||||
The file size of animated GIF output is optimised
|
||||
without having to use separate command line tools such as
|
||||
[gifsicle](https://www.lcdf.org/gifsicle/).
|
||||
|
||||
### Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
|
||||
|
||||
@@ -16,7 +16,7 @@ sharp('rgba.png')
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## ensureAlpha
|
||||
|
||||
@@ -47,7 +47,7 @@ const rgba = await sharp(rgb)
|
||||
|
||||
* Throws **[Error][3]** Invalid alpha transparency level
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -80,7 +80,7 @@ const [red1, red2, ...] = await sharp(input)
|
||||
|
||||
* Throws **[Error][3]** Invalid channel
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## joinChannel
|
||||
|
||||
@@ -104,7 +104,7 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
|
||||
|
||||
* Throws **[Error][3]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## bandbool
|
||||
|
||||
@@ -128,7 +128,7 @@ sharp('3-channel-rgb-input.png')
|
||||
|
||||
* Throws **[Error][3]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
[1]: /api-operation#flatten
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ 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.
|
||||
|
||||
<!---->
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input)
|
||||
.tint({ r: 255, g: 240, b: 16 })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
* Throws **[Error][4]** Invalid parameter
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## greyscale
|
||||
|
||||
@@ -28,7 +34,13 @@ An alpha channel may be present, and will be unchanged by the operation.
|
||||
|
||||
* `greyscale` **[Boolean][5]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input).greyscale().toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## grayscale
|
||||
|
||||
@@ -38,7 +50,7 @@ Alternative spelling of `greyscale`.
|
||||
|
||||
* `grayscale` **[Boolean][5]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## pipelineColourspace
|
||||
|
||||
@@ -65,7 +77,7 @@ await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -83,7 +95,7 @@ Alternative spelling of `pipelineColourspace`.
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## toColourspace
|
||||
|
||||
@@ -105,7 +117,7 @@ await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## toColorspace
|
||||
|
||||
@@ -119,7 +131,7 @@ Alternative spelling of `toColourspace`.
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ Composite image(s) over the processed (resized, extracted etc.) image.
|
||||
The images to composite must be the same size or smaller than the processed image.
|
||||
If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||
|
||||
Any resize or rotate operations in the same processing pipeline
|
||||
will always be applied to the input image before composition.
|
||||
|
||||
The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||
`dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||
`xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||
@@ -14,7 +17,7 @@ The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||
`hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||
|
||||
More information about blend modes can be found at
|
||||
[https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
|
||||
[https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode][1]
|
||||
and [https://www.cairographics.org/operators/][2]
|
||||
|
||||
### Parameters
|
||||
@@ -25,10 +28,22 @@ and [https://www.cairographics.org/operators/][2]
|
||||
|
||||
* `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
|
||||
|
||||
* `images[].input.create.width` **[Number][7]?**
|
||||
* `images[].input.create.height` **[Number][7]?**
|
||||
* `images[].input.create.width` **[Number][7]?** 
|
||||
* `images[].input.create.height` **[Number][7]?** 
|
||||
* `images[].input.create.channels` **[Number][7]?** 3-4
|
||||
* `images[].input.create.background` **([String][6] | [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
|
||||
* `images[].input.text` **[Object][4]?** describes a new text image to be created.
|
||||
|
||||
* `images[].input.text.text` **[string][6]?** text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||
* `images[].input.text.font` **[string][6]?** font name to render with.
|
||||
* `images[].input.text.fontfile` **[string][6]?** absolute filesystem path to a font file that can be used by `font`.
|
||||
* `images[].input.text.width` **[number][7]** integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. (optional, default `0`)
|
||||
* `images[].input.text.height` **[number][7]** integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. (optional, default `0`)
|
||||
* `images[].input.text.align` **[string][6]** text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). (optional, default `'left'`)
|
||||
* `images[].input.text.justify` **[boolean][9]** set this to true to apply justification to the text. (optional, default `false`)
|
||||
* `images[].input.text.dpi` **[number][7]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`)
|
||||
* `images[].input.text.rgba` **[boolean][9]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. (optional, default `false`)
|
||||
* `images[].input.text.spacing` **[number][7]** text line height in points. Will use the font line height if none is specified. (optional, default `0`)
|
||||
* `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
|
||||
* `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
|
||||
* `images[].top` **[Number][7]?** the pixel offset from the top edge.
|
||||
@@ -38,14 +53,32 @@ and [https://www.cairographics.org/operators/][2]
|
||||
* `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
|
||||
* `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
|
||||
|
||||
* `images[].raw.width` **[Number][7]?**
|
||||
* `images[].raw.height` **[Number][7]?**
|
||||
* `images[].raw.channels` **[Number][7]?**
|
||||
* `images[].failOnError` **[boolean][9]** @see [constructor parameters][10] (optional, default `true`)
|
||||
* `images[].raw.width` **[Number][7]?** 
|
||||
* `images[].raw.height` **[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[].failOn` **[string][6]** @see [constructor parameters][10] (optional, default `'warning'`)
|
||||
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)
|
||||
|
||||
### 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
|
||||
sharp('input.png')
|
||||
.rotate(180)
|
||||
@@ -65,13 +98,13 @@ sharp('input.png')
|
||||
|
||||
* Throws **[Error][11]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
* **since**: 0.22.0
|
||||
|
||||
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||
[1]: https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
|
||||
|
||||
[2]: https://www.cairographics.org/operators/
|
||||
|
||||
|
||||
@@ -20,38 +20,49 @@ Implements the [stream.Duplex][1] class.
|
||||
JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||
* `options` **[Object][13]?** if present, is an Object with optional attributes.
|
||||
|
||||
* `options.failOnError` **[boolean][14]** by default halt processing and raise an error when loading invalid images.
|
||||
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
|
||||
* `options.limitInputPixels` **([number][15] | [boolean][14])** Do not process input images where the number of pixels
|
||||
* `options.failOn` **[string][12]** when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default `'warning'`)
|
||||
* `options.limitInputPixels` **([number][14] | [boolean][15])** Do not process input images where the number of pixels
|
||||
(width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
|
||||
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
|
||||
* `options.unlimited` **[boolean][14]** Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG). (optional, default `false`)
|
||||
* `options.sequentialRead` **[boolean][14]** Set this to `true` to use sequential rather than random access where possible.
|
||||
* `options.unlimited` **[boolean][15]** Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). (optional, default `false`)
|
||||
* `options.sequentialRead` **[boolean][15]** Set this to `true` to use sequential rather than random access where possible.
|
||||
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
|
||||
* `options.density` **[number][15]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
|
||||
* `options.pages` **[number][15]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
||||
* `options.page` **[number][15]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
|
||||
* `options.subifd` **[number][15]** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default `-1`)
|
||||
* `options.level` **[number][15]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
|
||||
* `options.animated` **[boolean][14]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
|
||||
* `options.density` **[number][14]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
|
||||
* `options.pages` **[number][14]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
||||
* `options.page` **[number][14]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
|
||||
* `options.subifd` **[number][14]** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default `-1`)
|
||||
* `options.level` **[number][14]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
|
||||
* `options.animated` **[boolean][15]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
|
||||
* `options.raw` **[Object][13]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||
|
||||
* `options.raw.width` **[number][15]?** integral number of pixels wide.
|
||||
* `options.raw.height` **[number][15]?** integral number of pixels high.
|
||||
* `options.raw.channels` **[number][15]?** integral number of channels, between 1 and 4.
|
||||
* `options.raw.premultiplied` **[boolean][14]?** specifies that the raw input has already been premultiplied, set to `true`
|
||||
* `options.raw.width` **[number][14]?** integral number of pixels wide.
|
||||
* `options.raw.height` **[number][14]?** integral number of pixels high.
|
||||
* `options.raw.channels` **[number][14]?** integral number of channels, between 1 and 4.
|
||||
* `options.raw.premultiplied` **[boolean][15]?** specifies that the raw input has already been premultiplied, set to `true`
|
||||
to avoid sharp premultiplying the image. (optional, default `false`)
|
||||
* `options.create` **[Object][13]?** describes a new image to be created.
|
||||
|
||||
* `options.create.width` **[number][15]?** integral number of pixels wide.
|
||||
* `options.create.height` **[number][15]?** integral number of pixels high.
|
||||
* `options.create.channels` **[number][15]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
|
||||
* `options.create.width` **[number][14]?** integral number of pixels wide.
|
||||
* `options.create.height` **[number][14]?** integral number of pixels high.
|
||||
* `options.create.channels` **[number][14]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
|
||||
* `options.create.background` **([string][12] | [Object][13])?** parsed by the [color][16] module to extract values for red, green, blue and alpha.
|
||||
* `options.create.noise` **[Object][13]?** describes a noise to be created.
|
||||
|
||||
* `options.create.noise.type` **[string][12]?** type of generated noise, currently only `gaussian` is supported.
|
||||
* `options.create.noise.mean` **[number][15]?** mean of pixels in generated noise.
|
||||
* `options.create.noise.sigma` **[number][15]?** standard deviation of pixels in generated noise.
|
||||
* `options.create.noise.mean` **[number][14]?** mean of pixels in generated noise.
|
||||
* `options.create.noise.sigma` **[number][14]?** standard deviation of pixels in generated noise.
|
||||
* `options.text` **[Object][13]?** describes a new text image to be created.
|
||||
|
||||
* `options.text.text` **[string][12]?** text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||
* `options.text.font` **[string][12]?** font name to render with.
|
||||
* `options.text.fontfile` **[string][12]?** absolute filesystem path to a font file that can be used by `font`.
|
||||
* `options.text.width` **[number][14]** integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries. (optional, default `0`)
|
||||
* `options.text.height` **[number][14]** integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0. (optional, default `0`)
|
||||
* `options.text.align` **[string][12]** text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). (optional, default `'left'`)
|
||||
* `options.text.justify` **[boolean][15]** set this to true to apply justification to the text. (optional, default `false`)
|
||||
* `options.text.dpi` **[number][14]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`)
|
||||
* `options.text.rgba` **[boolean][15]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. (optional, default `false`)
|
||||
* `options.text.spacing` **[number][14]** text line height in points. Will use the font line height if none is specified. (optional, default `0`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -128,9 +139,32 @@ await sharp({
|
||||
}).toFile('noise.png');
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Generate an image from text
|
||||
await sharp({
|
||||
text: {
|
||||
text: 'Hello, world!',
|
||||
width: 400, // max width
|
||||
height: 300 // max height
|
||||
}
|
||||
}).toFile('text_bw.png');
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Generate an rgba image from text using pango markup and font
|
||||
await sharp({
|
||||
text: {
|
||||
text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||
font: 'sans',
|
||||
rgba: true,
|
||||
dpi: 300
|
||||
}
|
||||
}).toFile('text_rgba.png');
|
||||
```
|
||||
|
||||
* Throws **[Error][17]** Invalid parameters
|
||||
|
||||
Returns **[Sharp][18]**
|
||||
Returns **[Sharp][18]** 
|
||||
|
||||
## clone
|
||||
|
||||
@@ -154,9 +188,7 @@ readableStream.pipe(pipeline);
|
||||
// Using Promises to know when the pipeline is complete
|
||||
const fs = require("fs");
|
||||
const got = require("got");
|
||||
const sharpStream = sharp({
|
||||
failOnError: false
|
||||
});
|
||||
const sharpStream = sharp({ failOn: 'none' });
|
||||
|
||||
const promises = [];
|
||||
|
||||
@@ -183,7 +215,7 @@ promises.push(
|
||||
.toFile("optimized-500.webp")
|
||||
);
|
||||
|
||||
// https://github.com/sindresorhus/got#gotstreamurl-options
|
||||
// https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
|
||||
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
|
||||
|
||||
Promise.all(promises)
|
||||
@@ -198,7 +230,7 @@ Promise.all(promises)
|
||||
});
|
||||
```
|
||||
|
||||
Returns **[Sharp][18]**
|
||||
Returns **[Sharp][18]** 
|
||||
|
||||
[1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
|
||||
|
||||
@@ -226,9 +258,9 @@ Returns **[Sharp][18]**
|
||||
|
||||
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[16]: https://www.npmjs.org/package/color
|
||||
|
||||
|
||||
@@ -7,15 +7,18 @@ Fast access to (uncached) image metadata without decoding any compressed pixel d
|
||||
This is taken from the header of the input image.
|
||||
It does not include operations, such as resize, to be applied to the output image.
|
||||
|
||||
Dimensions in the response will respect the `page` and `pages` properties of the
|
||||
[constructor parameters][1].
|
||||
|
||||
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`
|
||||
* `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)
|
||||
* `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
|
||||
* `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, see example below)
|
||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][2]
|
||||
* `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` [...][3]
|
||||
* `density`: Number of pixels per inch (DPI), if present
|
||||
* `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
* `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
@@ -33,14 +36,14 @@ A `Promise` is returned when `callback` is not provided.
|
||||
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||
* `orientation`: Number value of the EXIF Orientation header, if present
|
||||
* `exif`: Buffer containing raw EXIF data, if present
|
||||
* `icc`: Buffer containing raw [ICC][3] profile data, if present
|
||||
* `icc`: Buffer containing raw [ICC][4] profile data, if present
|
||||
* `iptc`: Buffer containing raw IPTC data, if present
|
||||
* `xmp`: Buffer containing raw XMP data, if present
|
||||
* `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
|
||||
* `tifftagPhotoshop`: Buffer containing raw TIFFTAG\_PHOTOSHOP data, if present
|
||||
|
||||
### Parameters
|
||||
|
||||
* `callback` **[Function][4]?** called with the arguments `(err, metadata)`
|
||||
* `callback` **[Function][5]?** called with the arguments `(err, metadata)`
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -63,7 +66,19 @@ image
|
||||
});
|
||||
```
|
||||
|
||||
Returns **([Promise][5]<[Object][6]> | Sharp)**
|
||||
```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][6]<[Object][7]> | Sharp)** 
|
||||
|
||||
## stats
|
||||
|
||||
@@ -82,16 +97,16 @@ A `Promise` is returned when `callback` is not provided.
|
||||
* `maxX` (x-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.
|
||||
* `entropy`: Histogram-based estimation of greyscale entropy, 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 (experimental)
|
||||
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (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.
|
||||
* `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
|
||||
written to a buffer in order to run `stats` on the result (see third example).
|
||||
|
||||
### Parameters
|
||||
|
||||
* `callback` **[Function][4]?** called with the arguments `(err, stats)`
|
||||
* `callback` **[Function][5]?** called with the arguments `(err, stats)`
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -117,16 +132,18 @@ const part = await image.extract(region).toBuffer();
|
||||
const stats = await sharp(part).stats();
|
||||
```
|
||||
|
||||
Returns **[Promise][5]<[Object][6]>**
|
||||
Returns **[Promise][6]<[Object][7]>** 
|
||||
|
||||
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
|
||||
[1]: /api-constructor#parameters
|
||||
|
||||
[2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
|
||||
[2]: https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation
|
||||
|
||||
[3]: https://www.npmjs.com/package/icc
|
||||
[3]: https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat
|
||||
|
||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
[4]: https://www.npmjs.com/package/icc
|
||||
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
@@ -16,8 +16,11 @@ Mirroring is supported and may infer the use of a flip operation.
|
||||
|
||||
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
Method order is important when both rotating and extracting regions,
|
||||
for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||
Only one rotation can occur per pipeline.
|
||||
Previous calls to `rotate` in the same pipeline will be ignored.
|
||||
|
||||
Method order is important when rotating, resizing and/or extracting regions,
|
||||
for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||
|
||||
### Parameters
|
||||
|
||||
@@ -40,31 +43,54 @@ const pipeline = sharp()
|
||||
readableStream.pipe(pipeline);
|
||||
```
|
||||
|
||||
```javascript
|
||||
const rotateThenResize = await sharp(input)
|
||||
.rotate(90)
|
||||
.resize({ width: 16, height: 8, fit: 'fill' })
|
||||
.toBuffer();
|
||||
const resizeThenRotate = await sharp(input)
|
||||
.resize({ width: 16, height: 8, fit: 'fill' })
|
||||
.rotate(90)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## flip
|
||||
|
||||
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 before rotation, if any.
|
||||
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `flip` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input).flip().toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## flop
|
||||
|
||||
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 before rotation, if any.
|
||||
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `flop` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input).flop().toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## affine
|
||||
|
||||
@@ -116,7 +142,7 @@ inputStream
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -125,21 +151,53 @@ Returns **Sharp**
|
||||
## sharpen
|
||||
|
||||
Sharpen the image.
|
||||
|
||||
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.
|
||||
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
||||
Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
|
||||
|
||||
See [libvips sharpen][8] operation.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||
* `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` **([Object][2] | [number][1])?** if present, is an Object with attributes
|
||||
|
||||
<!---->
|
||||
* `options.sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10000
|
||||
* `options.m1` **[number][1]** the level of sharpening to apply to "flat" areas, between 0 and 1000000 (optional, default `1.0`)
|
||||
* `options.m2` **[number][1]** the level of sharpening to apply to "jagged" areas, between 0 and 1000000 (optional, default `2.0`)
|
||||
* `options.x1` **[number][1]** threshold between "flat" and "jagged", between 0 and 1000000 (optional, default `2.0`)
|
||||
* `options.y2` **[number][1]** maximum amount of brightening, between 0 and 1000000 (optional, default `10.0`)
|
||||
* `options.y3` **[number][1]** maximum amount of darkening, between 0 and 1000000 (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
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## median
|
||||
|
||||
@@ -150,11 +208,19 @@ When used without parameters the default window is 3x3.
|
||||
|
||||
* `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
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## blur
|
||||
|
||||
@@ -184,17 +250,17 @@ const gaussianBlurred = await sharp(input)
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## flatten
|
||||
|
||||
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||
|
||||
See also [removeAlpha][8].
|
||||
See also [removeAlpha][9].
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][2]?**
|
||||
* `options` **[Object][2]?** 
|
||||
|
||||
* `options.background` **([string][3] | [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
|
||||
|
||||
@@ -206,7 +272,7 @@ await sharp(rgbaInput)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## gamma
|
||||
|
||||
@@ -227,7 +293,7 @@ Supply a second argument to use a different output gamma value, otherwise the fi
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## negate
|
||||
|
||||
@@ -235,11 +301,25 @@ Produce the "negative" of the image.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][2]?**
|
||||
* `options` **[Object][2]?** 
|
||||
|
||||
* `options.alpha` **[Boolean][6]** Whether or not to negate any alpha channel (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input)
|
||||
.negate()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input)
|
||||
.negate({ alpha: false })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## normalise
|
||||
|
||||
@@ -249,7 +329,13 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
|
||||
|
||||
* `normalise` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input).normalise().toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## normalize
|
||||
|
||||
@@ -259,18 +345,24 @@ Alternative spelling of normalise.
|
||||
|
||||
* `normalize` **[Boolean][6]** (optional, default `true`)
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const output = await sharp(input).normalize().toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
## clahe
|
||||
|
||||
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.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][2]**
|
||||
* `options` **[Object][2]** 
|
||||
|
||||
* `options.width` **[number][1]** integer width of the region in pixels.
|
||||
* `options.height` **[number][1]** integer height of the region in pixels.
|
||||
@@ -278,11 +370,20 @@ 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
|
||||
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
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -294,7 +395,7 @@ Convolve the image with the specified kernel.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `kernel` **[Object][2]**
|
||||
* `kernel` **[Object][2]** 
|
||||
|
||||
* `kernel.width` **[number][1]** width of the kernel in pixels.
|
||||
* `kernel.height` **[number][1]** height of the kernel in pixels.
|
||||
@@ -320,7 +421,7 @@ sharp(input)
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## threshold
|
||||
|
||||
@@ -329,7 +430,7 @@ Any pixel value greater than or equal to the threshold value will be set to 255,
|
||||
### Parameters
|
||||
|
||||
* `threshold` **[number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
|
||||
* `options` **[Object][2]?**
|
||||
* `options` **[Object][2]?** 
|
||||
|
||||
* `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
|
||||
* `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
|
||||
@@ -338,7 +439,7 @@ Any pixel value greater than or equal to the threshold value will be set to 255,
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## boolean
|
||||
|
||||
@@ -349,36 +450,54 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
|
||||
|
||||
### 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.
|
||||
* `options` **[Object][2]?**
|
||||
* `options` **[Object][2]?** 
|
||||
|
||||
* `options.raw` **[Object][2]?** describes operand when using raw pixel data.
|
||||
|
||||
* `options.raw.width` **[number][1]?**
|
||||
* `options.raw.height` **[number][1]?**
|
||||
* `options.raw.channels` **[number][1]?**
|
||||
* `options.raw.width` **[number][1]?** 
|
||||
* `options.raw.height` **[number][1]?** 
|
||||
* `options.raw.channels` **[number][1]?** 
|
||||
|
||||
<!---->
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## linear
|
||||
|
||||
Apply the linear formula a \* input + b to the image (levels adjustment)
|
||||
Apply the linear formula `a` \* input + `b` to the image to adjust image levels.
|
||||
|
||||
When a single number is provided, it will be used for all image channels.
|
||||
When an array of numbers is provided, the array length must match the number of channels.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `a` **[number][1]** multiplier (optional, default `1.0`)
|
||||
* `b` **[number][1]** offset (optional, default `0.0`)
|
||||
* `a` **([number][1] | [Array][7]<[number][1]>)** multiplier (optional, default `[]`)
|
||||
* `b` **([number][1] | [Array][7]<[number][1]>)** offset (optional, default `[]`)
|
||||
|
||||
<!---->
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
await sharp(input)
|
||||
.linear(0.5, 2)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
await sharp(rgbInput)
|
||||
.linear(
|
||||
[0.25, 0.5, 0.75],
|
||||
[150, 100, 50]
|
||||
)
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## recomb
|
||||
|
||||
@@ -406,7 +525,7 @@ sharp(input)
|
||||
|
||||
* Throws **[Error][5]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -420,7 +539,7 @@ brightness is multiplicative whereas lightness is additive.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][2]?**
|
||||
* `options` **[Object][2]?** 
|
||||
|
||||
* `options.brightness` **[number][1]?** Brightness multiplier
|
||||
* `options.saturation` **[number][1]?** Saturation multiplier
|
||||
@@ -430,31 +549,44 @@ brightness is multiplicative whereas lightness is additive.
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
// increase brightness by a factor of 2
|
||||
const output = await sharp(input)
|
||||
.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({
|
||||
hue: 180 // hue-rotate by 180 degrees
|
||||
});
|
||||
hue: 180
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
sharp(input)
|
||||
```javascript
|
||||
// increase lightness by +50
|
||||
const output = await sharp(input)
|
||||
.modulate({
|
||||
lightness: 50 // increase lightness by +50
|
||||
});
|
||||
lightness: 50
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
sharp(input)
|
||||
const output = await sharp(input)
|
||||
.modulate({
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
hue: 90
|
||||
});
|
||||
hue: 90,
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -474,8 +606,10 @@ Returns **Sharp**
|
||||
|
||||
[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
|
||||
|
||||
@@ -22,6 +22,7 @@ A `Promise` is returned when `callback` is not provided.
|
||||
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -46,6 +47,8 @@ Returns **[Promise][5]<[Object][6]>** when no callback is provided
|
||||
Write output to a Buffer.
|
||||
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||
|
||||
Use [toFormat][7] or one of the format-specific functions such as [jpeg][8], [png][9] etc. to set the output format.
|
||||
|
||||
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
|
||||
|
||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
@@ -56,18 +59,18 @@ See [withMetadata][1] for control over this.
|
||||
* `err` is an error, if any.
|
||||
* `data` is the output image data.
|
||||
* `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||
|
||||
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||
|
||||
A `Promise` is returned when `callback` is not provided.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?**
|
||||
* `options` **[Object][6]?** 
|
||||
|
||||
* `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
* `callback` **[Function][3]?**
|
||||
* `options.resolveWithObject` **[boolean][10]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
* `callback` **[Function][3]?** 
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -85,6 +88,7 @@ sharp(input)
|
||||
|
||||
```javascript
|
||||
sharp(input)
|
||||
.png()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(({ data, info }) => { ... })
|
||||
.catch(err => { ... });
|
||||
@@ -107,7 +111,7 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
.toFile('my-changed-image.jpg');
|
||||
```
|
||||
|
||||
Returns **[Promise][5]<[Buffer][8]>** when no callback is provided
|
||||
Returns **[Promise][5]<[Buffer][11]>** when no callback is provided
|
||||
|
||||
## withMetadata
|
||||
|
||||
@@ -118,14 +122,16 @@ output profile is provided.
|
||||
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
|
||||
EXIF metadata is unsupported for TIFF output.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?**
|
||||
* `options` **[Object][6]?** 
|
||||
|
||||
* `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* `options.orientation` **[number][12]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
|
||||
* `options.exif` **[Object][6]<[Object][6]>** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default `{}`)
|
||||
* `options.density` **[number][9]?** Number of pixels per inch (DPI).
|
||||
* `options.density` **[number][12]?** Number of pixels per inch (DPI).
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -158,7 +164,7 @@ const data = await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## toFormat
|
||||
|
||||
@@ -180,7 +186,7 @@ const data = await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** unsupported format or options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## jpeg
|
||||
|
||||
@@ -190,19 +196,19 @@ Use these JPEG options for output image.
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.progressive` **[boolean][10]** use progressive (interlace) scan (optional, default `false`)
|
||||
* `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`)
|
||||
* `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
|
||||
* `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
|
||||
* `options.mozjpeg` **[boolean][7]** use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` (optional, default `false`)
|
||||
* `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation (optional, default `false`)
|
||||
* `options.overshootDeringing` **[boolean][7]** apply overshoot deringing (optional, default `false`)
|
||||
* `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive (optional, default `false`)
|
||||
* `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
|
||||
* `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8 (optional, default `0`)
|
||||
* `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable (optional, default `0`)
|
||||
* `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.optimiseCoding` **[boolean][10]** optimise Huffman coding tables (optional, default `true`)
|
||||
* `options.optimizeCoding` **[boolean][10]** alternative spelling of optimiseCoding (optional, default `true`)
|
||||
* `options.mozjpeg` **[boolean][10]** use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` (optional, default `false`)
|
||||
* `options.trellisQuantisation` **[boolean][10]** apply trellis quantisation (optional, default `false`)
|
||||
* `options.overshootDeringing` **[boolean][10]** apply overshoot deringing (optional, default `false`)
|
||||
* `options.optimiseScans` **[boolean][10]** optimise progressive scans, forces progressive (optional, default `false`)
|
||||
* `options.optimizeScans` **[boolean][10]** alternative spelling of optimiseScans (optional, default `false`)
|
||||
* `options.quantisationTable` **[number][12]** quantization table to use, integer 0-8 (optional, default `0`)
|
||||
* `options.quantizationTable` **[number][12]** alternative spelling of quantisationTable (optional, default `0`)
|
||||
* `options.force` **[boolean][10]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -225,7 +231,7 @@ const data = await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## png
|
||||
|
||||
@@ -237,18 +243,18 @@ Set `palette` to `true` for slower, indexed PNG output.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?**
|
||||
* `options` **[Object][6]?** 
|
||||
|
||||
* `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
|
||||
* `options.compressionLevel` **[number][9]** zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) (optional, default `6`)
|
||||
* `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
|
||||
* `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support (optional, default `false`)
|
||||
* `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`)
|
||||
* `options.effort` **[number][9]** CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true` (optional, default `7`)
|
||||
* `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`)
|
||||
* `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`)
|
||||
* `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`)
|
||||
* `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.progressive` **[boolean][10]** use progressive (interlace) scan (optional, default `false`)
|
||||
* `options.compressionLevel` **[number][12]** zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) (optional, default `6`)
|
||||
* `options.adaptiveFiltering` **[boolean][10]** use adaptive row filtering (optional, default `false`)
|
||||
* `options.palette` **[boolean][10]** quantise to a palette-based image with alpha transparency support (optional, default `false`)
|
||||
* `options.quality` **[number][12]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true` (optional, default `7`)
|
||||
* `options.colours` **[number][12]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`)
|
||||
* `options.colors` **[number][12]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`)
|
||||
* `options.dither` **[number][12]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`)
|
||||
* `options.force` **[boolean][10]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -268,7 +274,7 @@ const data = await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## webp
|
||||
|
||||
@@ -278,15 +284,17 @@ Use these WebP options for output image.
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.alphaQuality` **[number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
|
||||
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
|
||||
* `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
|
||||
* `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
|
||||
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 6 (slowest) (optional, default `4`)
|
||||
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
* `options.delay` **([number][9] | [Array][10]<[number][9]>)?** delay(s) between animation frames (in milliseconds)
|
||||
* `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.alphaQuality` **[number][12]** quality of alpha layer, integer 0-100 (optional, default `100`)
|
||||
* `options.lossless` **[boolean][10]** use lossless compression mode (optional, default `false`)
|
||||
* `options.nearLossless` **[boolean][10]** use near\_lossless compression mode (optional, default `false`)
|
||||
* `options.smartSubsample` **[boolean][10]** use high quality chroma subsampling (optional, default `false`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 0 (fastest) and 6 (slowest) (optional, default `4`)
|
||||
* `options.loop` **[number][12]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
* `options.delay` **([number][12] | [Array][13]<[number][12]>)?** delay(s) between animation frames (in milliseconds)
|
||||
* `options.minSize` **[boolean][10]** prevent use of animation key frames to minimise file size (slow) (optional, default `false`)
|
||||
* `options.mixed` **[boolean][10]** allow mixture of lossy and lossless animation frames (slow) (optional, default `false`)
|
||||
* `options.force` **[boolean][10]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -306,7 +314,7 @@ const outputWebp = await sharp(inputWebp, { animated: true })
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## gif
|
||||
|
||||
@@ -314,17 +322,23 @@ Use these GIF options for the output image.
|
||||
|
||||
The first entry in the palette is reserved for transparency.
|
||||
|
||||
The palette of the input image will be re-used if possible.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.colours` **[number][9]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`)
|
||||
* `options.colors` **[number][9]** alternative spelling of `options.colours` (optional, default `256`)
|
||||
* `options.effort` **[number][9]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`)
|
||||
* `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default `1.0`)
|
||||
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
* `options.delay` **([number][9] | [Array][10]<[number][9]>)?** delay(s) between animation frames (in milliseconds)
|
||||
* `options.force` **[boolean][7]** force GIF output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.reoptimise` **[boolean][10]** always generate new palettes (slow), re-use existing by default (optional, default `false`)
|
||||
* `options.reoptimize` **[boolean][10]** alternative spelling of `options.reoptimise` (optional, default `false`)
|
||||
* `options.colours` **[number][12]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`)
|
||||
* `options.colors` **[number][12]** alternative spelling of `options.colours` (optional, default `256`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`)
|
||||
* `options.dither` **[number][12]** level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default `1.0`)
|
||||
* `options.interFrameMaxError` **[number][12]** maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default `0`)
|
||||
* `options.interPaletteMaxError` **[number][12]** maximum inter-palette error for palette reuse, between 0 and 256 (optional, default `3`)
|
||||
* `options.loop` **[number][12]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
|
||||
* `options.delay` **([number][12] | [Array][13]<[number][12]>)?** delay(s) between animation frames (in milliseconds)
|
||||
* `options.force` **[boolean][10]** force GIF output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -332,7 +346,7 @@ The first entry in the palette is reserved for transparency.
|
||||
// Convert PNG to GIF
|
||||
await sharp(pngBuffer)
|
||||
.gif()
|
||||
.toBuffer());
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
@@ -349,9 +363,16 @@ const out = await sharp('in.gif', { animated: true })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Lossy file size reduction of animated GIF
|
||||
await sharp('in.gif', { animated: true })
|
||||
.gif({ interFrameMaxError: 8 })
|
||||
.toFile('optim.gif');
|
||||
```
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -363,16 +384,16 @@ Use these JP2 options for output image.
|
||||
|
||||
Requires libvips compiled with support for OpenJPEG.
|
||||
The prebuilt binaries do not include this - see
|
||||
[installing a custom libvips][11].
|
||||
[installing a custom libvips][14].
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
|
||||
* `options.tileWidth` **[number][9]** horizontal tile size (optional, default `512`)
|
||||
* `options.tileHeight` **[number][9]** vertical tile size (optional, default `512`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.lossless` **[boolean][10]** use lossless compression mode (optional, default `false`)
|
||||
* `options.tileWidth` **[number][12]** horizontal tile size (optional, default `512`)
|
||||
* `options.tileHeight` **[number][12]** vertical tile size (optional, default `512`)
|
||||
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||
|
||||
### Examples
|
||||
@@ -396,7 +417,7 @@ const data = await sharp(input)
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -412,18 +433,18 @@ The `density` can be set in pixels/inch via [withMetadata][1] instead of providi
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.force` **[boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.compression` **[string][2]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `80`)
|
||||
* `options.force` **[boolean][10]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
|
||||
* `options.compression` **[string][2]** compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default `'jpeg'`)
|
||||
* `options.predictor` **[string][2]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
|
||||
* `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
|
||||
* `options.tile` **[boolean][7]** write a tiled tiff (optional, default `false`)
|
||||
* `options.tileWidth` **[number][9]** horizontal tile size (optional, default `256`)
|
||||
* `options.tileHeight` **[number][9]** vertical tile size (optional, default `256`)
|
||||
* `options.xres` **[number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
|
||||
* `options.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
|
||||
* `options.pyramid` **[boolean][10]** write an image pyramid (optional, default `false`)
|
||||
* `options.tile` **[boolean][10]** write a tiled tiff (optional, default `false`)
|
||||
* `options.tileWidth` **[number][12]** horizontal tile size (optional, default `256`)
|
||||
* `options.tileHeight` **[number][12]** vertical tile size (optional, default `256`)
|
||||
* `options.xres` **[number][12]** horizontal resolution in pixels/mm (optional, default `1.0`)
|
||||
* `options.yres` **[number][12]** vertical resolution in pixels/mm (optional, default `1.0`)
|
||||
* `options.resolutionUnit` **[string][2]** resolution unit options: inch, cm (optional, default `'inch'`)
|
||||
* `options.bitdepth` **[number][9]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
|
||||
* `options.bitdepth` **[number][12]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -440,7 +461,7 @@ sharp('input.svg')
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## avif
|
||||
|
||||
@@ -455,16 +476,28 @@ AVIF image sequences are not supported.
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `50`)
|
||||
* `options.lossless` **[boolean][10]** use lossless compression (optional, default `false`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
|
||||
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||
|
||||
<!---->
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const data = await sharp(input)
|
||||
.avif({ effort: 2 })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
const data = await sharp(input)
|
||||
.avif({ lossless: true })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -474,28 +507,66 @@ Returns **Sharp**
|
||||
|
||||
Use these HEIF options for output image.
|
||||
|
||||
Support for patent-encumbered HEIC images requires the use of a
|
||||
Support for patent-encumbered HEIC images using `hevc` compression requires the use of a
|
||||
globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||
* `options.quality` **[number][12]** quality, integer 1-100 (optional, default `50`)
|
||||
* `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
|
||||
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
|
||||
* `options.lossless` **[boolean][10]** use lossless compression (optional, default `false`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
|
||||
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
const data = await sharp(input)
|
||||
.heif({ compression: 'hevc' })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
* **since**: 0.23.0
|
||||
|
||||
## jxl
|
||||
|
||||
Use these JPEG-XL (JXL) options for output image.
|
||||
|
||||
This feature is experimental, please do not use in production systems.
|
||||
|
||||
Requires libvips compiled with support for libjxl.
|
||||
The prebuilt binaries do not include this - see
|
||||
[installing a custom libvips][14].
|
||||
|
||||
Image metadata (EXIF, XMP) is unsupported.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?** output options
|
||||
|
||||
* `options.distance` **[number][12]** maximum encoding error, between 0 (highest quality) and 15 (lowest quality) (optional, default `1.0`)
|
||||
* `options.quality` **[number][12]?** calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified
|
||||
* `options.decodingTier` **[number][12]** target decode speed tier, between 0 (highest quality) and 4 (lowest quality) (optional, default `0`)
|
||||
* `options.lossless` **[boolean][10]** use lossless compression (optional, default `false`)
|
||||
* `options.effort` **[number][12]** CPU effort, between 3 (fastest) and 9 (slowest) (optional, default `7`)
|
||||
|
||||
<!---->
|
||||
|
||||
* Throws **[Error][4]** Invalid options
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
* **since**: 0.23.0
|
||||
* **since**: 0.31.3
|
||||
|
||||
## raw
|
||||
|
||||
@@ -533,24 +604,28 @@ const data = await sharp('input.png')
|
||||
## tile
|
||||
|
||||
Use tile-based deep zoom (image pyramid) output.
|
||||
|
||||
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
||||
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
||||
|
||||
The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]?**
|
||||
* `options` **[Object][6]?** 
|
||||
|
||||
* `options.size` **[number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||
* `options.overlap` **[number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||
* `options.angle` **[number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||
* `options.background` **([string][2] | [Object][6])** background colour, parsed by the [color][12] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
|
||||
* `options.size` **[number][12]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
|
||||
* `options.overlap` **[number][12]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
|
||||
* `options.angle` **[number][12]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
|
||||
* `options.background` **([string][2] | [Object][6])** background colour, parsed by the [color][15] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
|
||||
* `options.depth` **[string][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||
* `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
||||
* `options.skipBlanks` **[number][12]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
||||
* `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||
* `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
* `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
|
||||
* `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
|
||||
* `options.centre` **[boolean][10]** centre image in tile. (optional, default `false`)
|
||||
* `options.center` **[boolean][10]** alternative spelling of centre. (optional, default `false`)
|
||||
* `options.id` **[string][2]** when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
|
||||
* `options.basename` **[string][2]?** the name of the directory within the zip file when container is `zip`.
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -566,9 +641,22 @@ sharp('input.tiff')
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
const zipFileWithTiles = await sharp(input)
|
||||
.tile({ basename: "tiles" })
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
```javascript
|
||||
const iiififier = sharp().tile({ layout: "iiif" });
|
||||
readableStream
|
||||
.pipe(iiififier)
|
||||
.pipe(writeableStream);
|
||||
```
|
||||
|
||||
* Throws **[Error][4]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## timeout
|
||||
|
||||
@@ -580,11 +668,25 @@ Time spent waiting for a libuv thread to become available is not included.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `options` **[Object][6]**
|
||||
* `options` **[Object][6]** 
|
||||
|
||||
* `options.seconds` **[number][9]** Number of seconds after which processing will be stopped
|
||||
* `options.seconds` **[number][12]** Number of seconds after which processing will be stopped
|
||||
|
||||
Returns **Sharp**
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Ensure processing takes no longer than 3 seconds
|
||||
try {
|
||||
const data = await sharp(input)
|
||||
.blur(1000)
|
||||
.timeout({ seconds: 3 })
|
||||
.toBuffer();
|
||||
} catch (err) {
|
||||
if (err.message.includes('timeout')) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Returns **Sharp** 
|
||||
|
||||
**Meta**
|
||||
|
||||
@@ -602,14 +704,20 @@ Returns **Sharp**
|
||||
|
||||
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
[7]: #toformat
|
||||
|
||||
[8]: https://nodejs.org/api/buffer.html
|
||||
[8]: #jpeg
|
||||
|
||||
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
[9]: #png
|
||||
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[11]: https://sharp.pixelplumbing.com/install#custom-libvips
|
||||
[11]: https://nodejs.org/api/buffer.html
|
||||
|
||||
[12]: https://www.npmjs.org/package/color
|
||||
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||
|
||||
[14]: https://sharp.pixelplumbing.com/install#custom-libvips
|
||||
|
||||
[15]: https://www.npmjs.org/package/color
|
||||
|
||||
@@ -14,7 +14,9 @@ When both a `width` and `height` are provided, the possible methods by which the
|
||||
|
||||
Some of these values are based on the [object-fit][1] CSS property.
|
||||
|
||||
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.png">
|
||||
|
||||
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||
|
||||
* `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||
* `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
||||
@@ -36,17 +38,20 @@ Possible interpolation kernels are:
|
||||
* `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
|
||||
* `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||
|
||||
Only one resize can occur per pipeline.
|
||||
Previous calls to `resize` in the same pipeline will be ignored.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
* `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
* `options` **[Object][9]?**
|
||||
* `options` **[Object][9]?** 
|
||||
|
||||
* `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
|
||||
* `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
|
||||
* `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this takes priority.
|
||||
* `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this takes priority.
|
||||
* `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
|
||||
* `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
|
||||
* `options.background` **([String][10] | [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
|
||||
* `options.background` **([String][10] | [Object][9])** background colour when `fit` is `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
|
||||
* `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
|
||||
* `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
|
||||
* `options.withoutReduction` **[Boolean][12]** do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option. (optional, default `false`)
|
||||
@@ -144,7 +149,7 @@ const scaleByHalf = await sharp(input)
|
||||
|
||||
* Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## extend
|
||||
|
||||
@@ -190,7 +195,7 @@ sharp(input)
|
||||
|
||||
* Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## extract
|
||||
|
||||
@@ -231,25 +236,70 @@ sharp(input)
|
||||
|
||||
* Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
## trim
|
||||
|
||||
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||
Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||
|
||||
Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
||||
|
||||
If the result of this operation would trim an image to nothing then no change is made.
|
||||
|
||||
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
|
||||
* `trim` **([string][10] | [number][8] | [Object][9])** the specific background colour to trim, the threshold for doing so or an Object with both.
|
||||
|
||||
<!---->
|
||||
* `trim.background` **([string][10] | [Object][9])** background colour, parsed by the [color][11] module, defaults to that of the top-left pixel. (optional, default `'top-left pixel'`)
|
||||
* `trim.threshold` **[number][8]** the allowed difference from the above colour, a positive number. (optional, default `10`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Trim pixels with a colour similar to that of the top-left pixel.
|
||||
sharp(input)
|
||||
.trim()
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Trim pixels with the exact same colour as that of the top-left pixel.
|
||||
sharp(input)
|
||||
.trim(0)
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Trim only pixels with a similar colour to red.
|
||||
sharp(input)
|
||||
.trim("#FF0000")
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||
sharp(input)
|
||||
.trim({
|
||||
background: "yellow",
|
||||
threshold: 42,
|
||||
})
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
* Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
Returns **Sharp** 
|
||||
|
||||
[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ An Object containing nested boolean values representing the available input and
|
||||
console.log(sharp.format);
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
Returns **[Object][1]** 
|
||||
|
||||
## interpolators
|
||||
|
||||
@@ -90,12 +90,16 @@ sharp.cache( { files: 0 } );
|
||||
sharp.cache(false);
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
Returns **[Object][1]** 
|
||||
|
||||
## concurrency
|
||||
|
||||
Gets or, when a concurrency is provided, sets
|
||||
the number of threads *libvips'* should create to process each image.
|
||||
the maximum number of threads *libvips* should use to process *each image*.
|
||||
These are from a thread pool managed by glib,
|
||||
which helps avoid the overhead of creating new threads.
|
||||
|
||||
This method always returns the current concurrency.
|
||||
|
||||
The default value is the number of CPU cores,
|
||||
except when using glibc-based Linux without jemalloc,
|
||||
@@ -103,14 +107,23 @@ where the default is `1` to help reduce memory fragmentation.
|
||||
|
||||
A value of `0` will reset this to the number of CPU cores.
|
||||
|
||||
The maximum number of images that can be processed in parallel
|
||||
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
Some image format libraries spawn additional threads,
|
||||
e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||
and these are independent of the value set here.
|
||||
|
||||
This method always returns the current concurrency.
|
||||
The maximum number of images that sharp can process in parallel
|
||||
is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
|
||||
which defaults to 4.
|
||||
|
||||
[https://nodejs.org/api/cli.html#uv\_threadpool\_sizesize][12]
|
||||
|
||||
For example, by default, a machine with 8 CPU cores will process
|
||||
4 images in parallel and use up to 8 threads per image,
|
||||
so there will be up to 32 concurrent threads.
|
||||
|
||||
### Parameters
|
||||
|
||||
* `concurrency` **[number][11]?**
|
||||
* `concurrency` **[number][11]?** 
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -150,7 +163,7 @@ Provides access to internal task counters.
|
||||
const counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||
```
|
||||
|
||||
Returns **[Object][1]**
|
||||
Returns **[Object][1]** 
|
||||
|
||||
## simd
|
||||
|
||||
@@ -176,7 +189,7 @@ const simd = sharp.simd(false);
|
||||
// prevent libvips from using liborc at runtime
|
||||
```
|
||||
|
||||
Returns **[boolean][10]**
|
||||
Returns **[boolean][10]** 
|
||||
|
||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||
|
||||
@@ -199,3 +212,5 @@ Returns **[boolean][10]**
|
||||
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||
|
||||
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||
|
||||
[12]: https://nodejs.org/api/cli.html#uv_threadpool_sizesize
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const documentation = require('documentation');
|
||||
|
||||
[
|
||||
'constructor',
|
||||
@@ -15,6 +14,8 @@ const documentation = require('documentation');
|
||||
'output',
|
||||
'utility'
|
||||
].forEach(async (m) => {
|
||||
const documentation = await import('documentation');
|
||||
|
||||
const input = path.join('lib', `${m}.js`);
|
||||
const output = path.join('docs', `api-${m}.md`);
|
||||
|
||||
|
||||
@@ -1,9 +1,218 @@
|
||||
# Changelog
|
||||
|
||||
## v0.31 - *eagle*
|
||||
|
||||
Requires libvips v8.13.3
|
||||
|
||||
### v0.31.3 - 21st December 2022
|
||||
|
||||
* Add experimental support for JPEG-XL images. Requires libvips compiled with libjxl.
|
||||
[#2731](https://github.com/lovell/sharp/issues/2731)
|
||||
|
||||
* Add runtime detection of V8 memory cage, ensures compatibility with Electron 21 onwards.
|
||||
[#3384](https://github.com/lovell/sharp/issues/3384)
|
||||
|
||||
* Expose `interFrameMaxError` and `interPaletteMaxError` GIF optimisation properties.
|
||||
[#3401](https://github.com/lovell/sharp/issues/3401)
|
||||
|
||||
* Allow installation on Linux with glibc patch versions e.g. Fedora 38.
|
||||
[#3423](https://github.com/lovell/sharp/issues/3423)
|
||||
|
||||
* Expand range of existing `sharpen` parameters to match libvips.
|
||||
[#3427](https://github.com/lovell/sharp/issues/3427)
|
||||
|
||||
* Prevent possible race condition awaiting metadata of Stream-based input.
|
||||
[#3451](https://github.com/lovell/sharp/issues/3451)
|
||||
|
||||
* Improve `extractChannel` support for 16-bit output colourspaces.
|
||||
[#3453](https://github.com/lovell/sharp/issues/3453)
|
||||
|
||||
* Ignore `sequentialRead` option when calculating image statistics.
|
||||
[#3462](https://github.com/lovell/sharp/issues/3462)
|
||||
|
||||
* Small performance improvement for operations that introduce a non-opaque background.
|
||||
[#3465](https://github.com/lovell/sharp/issues/3465)
|
||||
|
||||
* Ensure integral output of `linear` operation.
|
||||
[#3468](https://github.com/lovell/sharp/issues/3468)
|
||||
|
||||
### v0.31.2 - 4th November 2022
|
||||
|
||||
* Upgrade to libvips v8.13.3 for upstream bug fixes.
|
||||
|
||||
* Ensure manual flip, rotate, resize operation ordering (regression in 0.31.1)
|
||||
[#3391](https://github.com/lovell/sharp/issues/3391)
|
||||
|
||||
* Ensure auto-rotation works without resize (regression in 0.31.1)
|
||||
[#3422](https://github.com/lovell/sharp/issues/3422)
|
||||
|
||||
### v0.31.1 - 29th September 2022
|
||||
|
||||
* Upgrade to libvips v8.13.2 for upstream bug fixes.
|
||||
|
||||
* Ensure `close` event occurs after `end` event for Stream-based output.
|
||||
[#3313](https://github.com/lovell/sharp/issues/3313)
|
||||
|
||||
* Ensure `limitInputPixels` constructor option uses uint64.
|
||||
[#3349](https://github.com/lovell/sharp/pull/3349)
|
||||
[@marcosc90](https://github.com/marcosc90)
|
||||
|
||||
* Ensure auto-rotation works with shrink-on-load and extract (regression in 0.31.0).
|
||||
[#3352](https://github.com/lovell/sharp/issues/3352)
|
||||
|
||||
* Ensure AVIF output is always 8-bit.
|
||||
[#3358](https://github.com/lovell/sharp/issues/3358)
|
||||
|
||||
* Ensure greyscale images can be trimmed (regression in 0.31.0).
|
||||
[#3386](https://github.com/lovell/sharp/issues/3386)
|
||||
|
||||
### v0.31.0 - 5th September 2022
|
||||
|
||||
* Drop support for Node.js 12, now requires Node.js >= 14.15.0.
|
||||
|
||||
* GIF output now re-uses input palette if possible. Use `reoptimise` option to generate a new palette.
|
||||
|
||||
* Add WebP `minSize` and `mixed` options for greater control over animation frames.
|
||||
|
||||
* Remove previously-deprecated WebP `reductionEffort` and HEIF `speed` options. Use `effort` to control these.
|
||||
|
||||
* The `flip` and `flop` operations will now occur before the `rotate` operation.
|
||||
|
||||
* Improve `normalise` operation with use of histogram.
|
||||
[#200](https://github.com/lovell/sharp/issues/200)
|
||||
|
||||
* Use combined bounding box of alpha and non-alpha channels for `trim` operation.
|
||||
[#2166](https://github.com/lovell/sharp/issues/2166)
|
||||
|
||||
* Add Buffer and Stream support to tile-based output.
|
||||
[#2238](https://github.com/lovell/sharp/issues/2238)
|
||||
|
||||
* Add input `fileSuffix` and output `alias` to `format` information.
|
||||
[#2642](https://github.com/lovell/sharp/issues/2642)
|
||||
|
||||
* Re-introduce support for greyscale ICC profiles (temporarily removed in 0.30.2).
|
||||
[#3114](https://github.com/lovell/sharp/issues/3114)
|
||||
|
||||
* Add support for WebP and PackBits `compression` options with TIFF output.
|
||||
[#3198](https://github.com/lovell/sharp/issues/3198)
|
||||
|
||||
* Ensure OpenSlide and FITS input works with custom libvips.
|
||||
[#3226](https://github.com/lovell/sharp/issues/3226)
|
||||
|
||||
* Ensure `trim` operation is a no-op when it would reduce an image to nothing.
|
||||
[#3223](https://github.com/lovell/sharp/issues/3223)
|
||||
|
||||
* Expose `vips_text` to create an image containing rendered text.
|
||||
[#3252](https://github.com/lovell/sharp/pull/3252)
|
||||
[@brahima](https://github.com/brahima)
|
||||
|
||||
* Ensure only properties owned by the `withMetadata` EXIF Object are parsed.
|
||||
[#3292](https://github.com/lovell/sharp/issues/3292)
|
||||
|
||||
* Expand `linear` operation to allow use of per-channel arrays.
|
||||
[#3303](https://github.com/lovell/sharp/pull/3303)
|
||||
[@antonmarsden](https://github.com/antonmarsden)
|
||||
|
||||
* Ensure the order of `rotate`, `resize` and `extend` operations is respected where possible.
|
||||
Emit warnings when previous calls in the same pipeline will be ignored.
|
||||
[#3319](https://github.com/lovell/sharp/issues/3319)
|
||||
|
||||
* Ensure PNG bitdepth can be set for non-palette output.
|
||||
[#3322](https://github.com/lovell/sharp/issues/3322)
|
||||
|
||||
* Add trim option to provide a specific background colour.
|
||||
[#3332](https://github.com/lovell/sharp/pull/3332)
|
||||
[@mart-jansink](https://github.com/mart-jansink)
|
||||
|
||||
* Ensure resized image is unpremultiplied before composite.
|
||||
[#3334](https://github.com/lovell/sharp/issues/3334)
|
||||
|
||||
## v0.30 - *dresser*
|
||||
|
||||
Requires libvips v8.12.2
|
||||
|
||||
### v0.30.7 - 22nd June 2022
|
||||
|
||||
* Ensure tiled composition always works with outside resizing.
|
||||
[#3227](https://github.com/lovell/sharp/issues/3227)
|
||||
|
||||
* Allow WebP encoding effort of 0.
|
||||
[#3261](https://github.com/lovell/sharp/pull/3261)
|
||||
[@AlexanderTheGrey](https://github.com/AlexanderTheGrey)
|
||||
|
||||
* Prevent upsampling via libwebp.
|
||||
[#3267](https://github.com/lovell/sharp/pull/3267)
|
||||
[@blacha](https://github.com/blacha)
|
||||
|
||||
### v0.30.6 - 30th May 2022
|
||||
|
||||
* Allow values for `limitInputPixels` larger than 32-bit.
|
||||
[#3238](https://github.com/lovell/sharp/issues/3238)
|
||||
|
||||
* Ensure brew-installed `vips` can be detected (regression in 0.30.5).
|
||||
[#3239](https://github.com/lovell/sharp/issues/3239)
|
||||
|
||||
### v0.30.5 - 23rd May 2022
|
||||
|
||||
* Install: pass `PKG_CONFIG_PATH` via env rather than substitution.
|
||||
[@dwisiswant0](https://github.com/dwisiswant0)
|
||||
|
||||
* Add support for `--libc` flag to improve cross-platform installation.
|
||||
[#3160](https://github.com/lovell/sharp/pull/3160)
|
||||
[@joonamo](https://github.com/joonamo)
|
||||
|
||||
* Allow installation of prebuilt libvips binaries from filesystem.
|
||||
[#3196](https://github.com/lovell/sharp/pull/3196)
|
||||
[@ankurparihar](https://github.com/ankurparihar)
|
||||
|
||||
* Fix rotate-then-extract for EXIF orientation 2.
|
||||
[#3218](https://github.com/lovell/sharp/pull/3218)
|
||||
[@jakob0fischl](https://github.com/jakob0fischl)
|
||||
|
||||
### v0.30.4 - 18th April 2022
|
||||
|
||||
* Increase control over sensitivity to invalid images via `failOn`, deprecate `failOnError` (equivalent to `failOn: 'warning'`).
|
||||
|
||||
* Ensure `create` input image has correct bit depth and colour space.
|
||||
[#3139](https://github.com/lovell/sharp/issues/3139)
|
||||
|
||||
* Add support for `TypedArray` input with `byteOffset` and `length`.
|
||||
[#3146](https://github.com/lovell/sharp/pull/3146)
|
||||
[@codepage949](https://github.com/codepage949)
|
||||
|
||||
* Improve error message when attempting to render SVG input greater than 32767x32767.
|
||||
[#3167](https://github.com/lovell/sharp/issues/3167)
|
||||
|
||||
* Add missing file name to 'Input file is missing' error message.
|
||||
[#3178](https://github.com/lovell/sharp/pull/3178)
|
||||
[@Brodan](https://github.com/Brodan)
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -236,3 +236,30 @@ GitHub: https://github.com/gforge
|
||||
|
||||
Name: Chris Banks
|
||||
GitHub: https://github.com/christopherbradleybanks
|
||||
|
||||
Name: codepage949
|
||||
GitHub: https://github.com/codepage949
|
||||
|
||||
Name: Chris Hranj
|
||||
GitHub: https://github.com/Brodan
|
||||
|
||||
Name: Ankur Parihar
|
||||
GitHub: https://github.com/ankurparihar
|
||||
|
||||
Name: Joona Heinikoski
|
||||
GitHub: https://github.com/joonamo
|
||||
|
||||
Name: AlexanderTheGrey
|
||||
GitHub: https://github.com/AlexanderTheGrey
|
||||
|
||||
Name: Blayne Chard
|
||||
GitHub: https://github.com/blacha
|
||||
|
||||
Name: Brahim
|
||||
GitHub: https://github.com/brahima
|
||||
|
||||
Name: Anton Marsden
|
||||
GitHub: https://github.com/antonmarsden
|
||||
|
||||
Name: Marcos Casagrande
|
||||
GitHub: https://github.com/marcosc90
|
||||
|
||||
BIN
docs/image/api-resize-fit.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
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="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';
|
||||
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;
|
||||
script-src 'self' 'unsafe-inline' 'unsafe-eval'
|
||||
https://www.google-analytics.com/analytics.js;">
|
||||
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
|
||||
<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="icon" type="image/svg+xml" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/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="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">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -124,7 +118,7 @@
|
||||
router: { mode: 'history' },
|
||||
logo: '<div style="display:flex;align-items:center">'
|
||||
+ '<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">Node.js image processing</span> '
|
||||
+ '</div>',
|
||||
|
||||
148
docs/install.md
@@ -10,7 +10,7 @@ yarn add sharp
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Node.js >= 12.13.0
|
||||
* Node.js >= 14.15.0
|
||||
|
||||
## Prebuilt binaries
|
||||
|
||||
@@ -57,7 +57,7 @@ When using npm v7 or later, the user running `npm install` must own the director
|
||||
|
||||
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
|
||||
|
||||
Check the output of running `npm install --verbose sharp` for useful error messages.
|
||||
Check the output of running `npm install --verbose --foreground-scripts sharp` for useful error messages.
|
||||
|
||||
## Apple M1
|
||||
|
||||
@@ -74,20 +74,36 @@ The target platform and/or architecture can be manually selected using the follo
|
||||
npm install --platform=... --arch=... --arm-version=... sharp
|
||||
```
|
||||
|
||||
* `--platform`: one of `linux`, `linuxmusl`, `darwin` or `win32`.
|
||||
* `--platform`: one of `linux`, `darwin` or `win32`.
|
||||
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
|
||||
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
|
||||
* `--libc`: one of `glibc` or `musl`. This option only works with platform `linux`, defaults to `glibc`
|
||||
* `--sharp-install-force`: skip version compatibility and Subresource Integrity checks.
|
||||
|
||||
These values can also be set via environment variables,
|
||||
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`
|
||||
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`, `npm_config_libc`
|
||||
and `SHARP_INSTALL_FORCE` respectively.
|
||||
|
||||
For example, if the target machine has a 64-bit ARM CPU and is running Alpine Linux,
|
||||
use the following flags:
|
||||
|
||||
```sh
|
||||
npm install --arch=arm64 --platform=linuxmusl sharp
|
||||
npm install --arch=arm64 --platform=linux --libc=musl sharp
|
||||
```
|
||||
|
||||
If the current machine is Alpine Linux and the target machine is Debian Linux on x64 cpu,
|
||||
use the following flags:
|
||||
|
||||
```sh
|
||||
npm install --arch=x64 --platform=linux --libc=glibc sharp
|
||||
```
|
||||
|
||||
Multiple platforms and architectures can be supported within the same installation tree.
|
||||
The following example for macOS installs x64 binaries then adds (via a rebuild) arm64 binaries:
|
||||
|
||||
```sh
|
||||
npm install --platform=darwin --arch=x64 sharp
|
||||
npm rebuild --platform=darwin --arch=arm64 sharp
|
||||
```
|
||||
|
||||
## Custom libvips
|
||||
@@ -96,8 +112,8 @@ To use a custom, globally-installed version of libvips instead of the provided b
|
||||
make sure it is at least the version listed under `config.libvips` in the `package.json` file
|
||||
and that it can be located using `pkg-config --modversion vips-cpp`.
|
||||
|
||||
For help compiling libvips from source, please see
|
||||
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball).
|
||||
For help compiling libvips and its dependencies, please see
|
||||
[building libvips from source](https://www.libvips.org/install.html#building-libvips-from-source).
|
||||
|
||||
The use of a globally-installed libvips is unsupported on Windows.
|
||||
|
||||
@@ -118,6 +134,8 @@ Building from source requires:
|
||||
|
||||
This is an advanced approach that most people will not require.
|
||||
|
||||
### Prebuilt sharp binaries
|
||||
|
||||
To install the prebuilt sharp binaries from a custom URL,
|
||||
set the `sharp_binary_host` npm config option
|
||||
or the `npm_config_sharp_binary_host` environment variable.
|
||||
@@ -126,14 +144,37 @@ To install the prebuilt sharp binaries from a directory on the local filesystem,
|
||||
set the `sharp_local_prebuilds` npm config option
|
||||
or the `npm_config_sharp_local_prebuilds` environment variable.
|
||||
|
||||
URL example:
|
||||
if `sharp_binary_host` is set to `https://hostname/path`
|
||||
and the sharp version is `1.2.3` then the resultant URL will be
|
||||
`https://hostname/path/sharp-v1.2.3-napi-v5-platform-arch.tar.gz`.
|
||||
|
||||
Filename example:
|
||||
if `sharp_local_prebuilds` is set to `/path`
|
||||
and the sharp version is `1.2.3` then the resultant filename will be
|
||||
`/path/sharp-v1.2.3-napi-v5-platform-arch.tar.gz`.
|
||||
|
||||
### Prebuilt libvips binaries
|
||||
|
||||
To install the prebuilt libvips binaries from a custom URL,
|
||||
set the `sharp_libvips_binary_host` npm config option
|
||||
or the `npm_config_sharp_libvips_binary_host` environment variable.
|
||||
|
||||
The version subpath and file name are appended to these.
|
||||
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
|
||||
and the libvips version is `1.2.3` then the resultant URL will be
|
||||
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br`.
|
||||
To install the prebuilt libvips binaries from a directory on the local filesystem,
|
||||
set the `sharp_libvips_local_prebuilds` npm config option
|
||||
or the `npm_config_sharp_libvips_local_prebuilds` environment variable.
|
||||
|
||||
The version subpath and filename are appended to these.
|
||||
|
||||
URL example:
|
||||
if `sharp_libvips_binary_host` is set to `https://hostname/path`
|
||||
and the libvips version is `4.5.6` then the resultant URL will be
|
||||
`https://hostname/path/v4.5.6/libvips-4.5.6-platform-arch.tar.br`.
|
||||
|
||||
Filename example:
|
||||
if `sharp_libvips_local_prebuilds` is set to `/path`
|
||||
and the libvips version is `4.5.6` then the resultant filename will be
|
||||
`/path/v4.5.6/libvips-4.5.6-platform-arch.tar.br`.
|
||||
|
||||
See the Chinese mirror below for a further example.
|
||||
|
||||
@@ -207,15 +248,18 @@ run the following additional command after `npm install`:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
|
||||
rm -rf node_modules/sharp
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
|
||||
```
|
||||
|
||||
To get the best performance select the largest memory available.
|
||||
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/)
|
||||
configuration.
|
||||
|
||||
@@ -225,20 +269,86 @@ 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
|
||||
```
|
||||
|
||||
For `serverless-esbuild`, ensure platform-specific binaries are installed
|
||||
via the `serverless.yml` configuration.
|
||||
|
||||
```yaml
|
||||
custom:
|
||||
esbuild:
|
||||
external:
|
||||
- sharp
|
||||
packagerOptions:
|
||||
scripts:
|
||||
- npm install --arch=x64 --platform=linux sharp
|
||||
```
|
||||
|
||||
## Fonts
|
||||
|
||||
When creating text images or rendering SVG images that contain text elements,
|
||||
`fontconfig` is used to find the relevant fonts.
|
||||
|
||||
On Windows and macOS systems, all system fonts are available for use.
|
||||
|
||||
On macOS systems using Homebrew, you may need to set the
|
||||
`PANGOCAIRO_BACKEND` environment variable to a value of `fontconfig`
|
||||
to ensure it is used for font discovery instead of Core Text.
|
||||
|
||||
On Linux systems, fonts that include the relevant
|
||||
[`fontconfig` configuration](https://www.freedesktop.org/software/fontconfig/fontconfig-user.html)
|
||||
when installed via package manager are available for use.
|
||||
|
||||
If `fontconfig` configuration is not found, the following error will occur:
|
||||
```
|
||||
Fontconfig error: Cannot load default config file
|
||||
```
|
||||
|
||||
In serverless environments where there is no control over font packages,
|
||||
use the `FONTCONFIG_PATH` environment variable to point to a custom location.
|
||||
|
||||
Embedded SVG fonts are unsupported.
|
||||
|
||||
## Worker threads
|
||||
|
||||
The main thread must call `require('sharp')`
|
||||
before worker threads are created
|
||||
to ensure shared libraries remain loaded in memory
|
||||
On some platforms, including glibc-based Linux,
|
||||
the main thread must call `require('sharp')`
|
||||
_before_ worker threads are created.
|
||||
This is to ensure shared libraries remain loaded in memory
|
||||
until after all threads are complete.
|
||||
|
||||
Without this, the following error may occur:
|
||||
```
|
||||
Module did not self-register
|
||||
```
|
||||
|
||||
## Known conflicts
|
||||
|
||||
### Canvas and Windows
|
||||
|
||||
The prebuilt binaries provided by `canvas` for Windows depend on the unmaintained GTK 2, last updated in 2011.
|
||||
The prebuilt binaries provided by `canvas` for Windows
|
||||
from v2.7.0 onwards depend on the Visual C++ Runtime (MSVCRT).
|
||||
These conflict with the binaries provided by sharp,
|
||||
which depend on the more modern Universal C Runtime (UCRT).
|
||||
|
||||
These conflict with the modern, up-to-date binaries provided by sharp.
|
||||
See [Automattic/node-canvas#2155](https://github.com/Automattic/node-canvas/issues/2155).
|
||||
|
||||
If both modules are used in the same Windows process, the following error will occur:
|
||||
```
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
|
||||
A test to benchmark the performance of this module relative to alternatives.
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
|
||||
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
||||
|
||||
## The contenders
|
||||
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.9 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.16.2 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
||||
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* [gm](https://www.npmjs.com/package/gm) v1.25.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code.
|
||||
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.2 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process.
|
||||
* sharp v0.30.0 / libvips v8.12.2 - Caching within libvips disabled to ensure a fair comparison.
|
||||
* sharp v0.31.3 / libvips v8.13.3 - Caching within libvips disabled to ensure a fair comparison.
|
||||
|
||||
## The task
|
||||
|
||||
@@ -18,55 +22,50 @@ Decompress a 2725x2225 JPEG image,
|
||||
resize to 720x588 using Lanczos 3 resampling (where available),
|
||||
then compress to JPEG at a "quality" setting of 80.
|
||||
|
||||
## Test environment
|
||||
|
||||
* AWS EC2 eu-west-1 [c5ad.xlarge](https://aws.amazon.com/ec2/instance-types/c5/) (4x AMD EPYC 7R32)
|
||||
* Ubuntu 21.10 (ami-0258eeb71ddf238b3)
|
||||
* Node.js 16.13.2
|
||||
|
||||
## Results
|
||||
|
||||
### AMD64
|
||||
|
||||
* AWS EC2 eu-west-1 [c6a.xlarge](https://aws.amazon.com/ec2/instance-types/c6a/) (4x AMD EPYC 7R13)
|
||||
* Ubuntu 22.04 (ami-026e72e4e468afa7b)
|
||||
* Node.js 16.19.0
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 0.82 | 1.0 |
|
||||
| squoosh-cli | file | file | 1.05 | 1.3 |
|
||||
| squoosh-lib | buffer | buffer | 1.19 | 1.5 |
|
||||
| gm | buffer | buffer | 8.47 | 10.3 |
|
||||
| gm | file | file | 8.58 | 10.5 |
|
||||
| imagemagick | file | file | 9.23 | 11.3 |
|
||||
| sharp | stream | stream | 33.23 | 40.5 |
|
||||
| sharp | file | file | 35.22 | 43.0 |
|
||||
| sharp | buffer | buffer | 35.70 | 43.5 |
|
||||
|
||||
### ARM64
|
||||
|
||||
* AWS EC2 eu-west-1 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
|
||||
* Ubuntu 22.04 (ami-02142ceceb3933ff5)
|
||||
* Node.js 16.19.0
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 0.84 | 1.0 |
|
||||
| squoosh-cli | file | file | 1.08 | 1.3 |
|
||||
| squoosh-lib | buffer | buffer | 1.85 | 2.2 |
|
||||
| mapnik | buffer | buffer | 3.45 | 4.1 |
|
||||
| gm | buffer | buffer | 8.60 | 10.2 |
|
||||
| gm | file | file | 8.66 | 10.3 |
|
||||
| imagemagick | file | file | 8.79 | 10.5 |
|
||||
| sharp | stream | stream | 28.90 | 34.4 |
|
||||
| sharp | file | file | 30.08 | 35.8 |
|
||||
| sharp | buffer | buffer | 30.42 | 36.2 |
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
|
||||
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
||||
| squoosh-cli | file | file | 1.12 | 1.3 |
|
||||
| squoosh-lib | buffer | buffer | 2.11 | 2.5 |
|
||||
| gm | buffer | buffer | 10.39 | 12.4 |
|
||||
| gm | file | file | 10.40 | 12.4 |
|
||||
| imagemagick | file | file | 10.73 | 12.8 |
|
||||
| sharp | stream | stream | 33.63 | 40.0 |
|
||||
| sharp | file | file | 34.91 | 41.6 |
|
||||
| sharp | buffer | buffer | 35.72 | 42.5 |
|
||||
|
||||
## Running the benchmark test
|
||||
|
||||
Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
|
||||
|
||||
```sh
|
||||
brew install imagemagick
|
||||
brew install graphicsmagick
|
||||
brew install mapnik
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo apt-get install build-essential imagemagick libmagick++-dev graphicsmagick libmapnik-dev
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
|
||||
```
|
||||
Requires Docker.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/lovell/sharp.git
|
||||
cd sharp
|
||||
npm install --build-from-source
|
||||
cd test/bench
|
||||
npm install
|
||||
npm test
|
||||
cd sharp/test/bench
|
||||
./run-with-docker.sh
|
||||
```
|
||||
|
||||
@@ -9,7 +9,7 @@ const extractDescription = (str) =>
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.substr(0, 180)
|
||||
.substring(0, 200)
|
||||
.trim();
|
||||
|
||||
const extractParameters = (str) =>
|
||||
|
||||
@@ -12,6 +12,7 @@ module.exports = [
|
||||
'and',
|
||||
'any',
|
||||
'are',
|
||||
'available',
|
||||
'based',
|
||||
'been',
|
||||
'before',
|
||||
@@ -21,6 +22,7 @@ module.exports = [
|
||||
'can',
|
||||
'containing',
|
||||
'contains',
|
||||
'created',
|
||||
'current',
|
||||
'date',
|
||||
'default',
|
||||
@@ -43,6 +45,8 @@ module.exports = [
|
||||
'how',
|
||||
'image',
|
||||
'implies',
|
||||
'include',
|
||||
'including',
|
||||
'involve',
|
||||
'its',
|
||||
'last',
|
||||
@@ -69,14 +73,18 @@ module.exports = [
|
||||
'provided',
|
||||
'ready',
|
||||
'requires',
|
||||
'requiresharp',
|
||||
'returned',
|
||||
'run',
|
||||
'same',
|
||||
'see',
|
||||
'set',
|
||||
'sets',
|
||||
'sharp',
|
||||
'should',
|
||||
'since',
|
||||
'site',
|
||||
'some',
|
||||
'specified',
|
||||
'spelling',
|
||||
'such',
|
||||
@@ -111,5 +119,6 @@ module.exports = [
|
||||
'will',
|
||||
'with',
|
||||
'without',
|
||||
'you'
|
||||
'you',
|
||||
'your'
|
||||
];
|
||||
|
||||
@@ -35,6 +35,7 @@ const hasSharpPrebuild = [
|
||||
];
|
||||
|
||||
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
||||
const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || '';
|
||||
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
||||
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
||||
const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);
|
||||
@@ -42,7 +43,9 @@ const installationForced = !!(process.env.npm_config_sharp_install_force || proc
|
||||
const fail = function (err) {
|
||||
libvips.log(err);
|
||||
if (err.code === 'EACCES') {
|
||||
libvips.log('Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
|
||||
libvips.log('Are you trying to install as a root or sudo user?');
|
||||
libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag');
|
||||
libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in');
|
||||
}
|
||||
libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
|
||||
process.exit(1);
|
||||
@@ -135,7 +138,8 @@ try {
|
||||
const libcFamily = detectLibc.familySync();
|
||||
const libcVersion = detectLibc.versionSync();
|
||||
if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
|
||||
if (semverLessThan(`${libcVersion}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
|
||||
const libcVersionWithoutPatch = libcVersion.split('.').slice(0, 2).join('.');
|
||||
if (semverLessThan(`${libcVersionWithoutPatch}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
|
||||
handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
|
||||
}
|
||||
}
|
||||
@@ -156,10 +160,15 @@ try {
|
||||
if (fs.existsSync(tarPathCache)) {
|
||||
libvips.log(`Using cached ${tarPathCache}`);
|
||||
extractTarball(tarPathCache, platformAndArch);
|
||||
} else if (localLibvipsDir) {
|
||||
// If localLibvipsDir is given try to use binaries from local directory
|
||||
const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename);
|
||||
libvips.log(`Using local libvips from ${tarPathLocal}`);
|
||||
extractTarball(tarPathLocal, platformAndArch);
|
||||
} else {
|
||||
const url = distBaseUrl + tarFilename;
|
||||
libvips.log(`Downloading ${url}`);
|
||||
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
||||
simpleGet({ url: url, agent: agent(libvips.log) }, function (err, response) {
|
||||
if (err) {
|
||||
fail(err);
|
||||
} else if (response.statusCode === 404) {
|
||||
|
||||
@@ -18,7 +18,7 @@ function env (key) {
|
||||
return process.env[key];
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (log) {
|
||||
try {
|
||||
const proxy = new url.URL(proxies.map(env).find(is.string));
|
||||
const tunnel = proxy.protocol === 'https:'
|
||||
@@ -27,6 +27,7 @@ module.exports = function () {
|
||||
const proxyAuth = proxy.username && proxy.password
|
||||
? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
|
||||
: null;
|
||||
log(`Via proxy ${proxy.protocol}://${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`);
|
||||
return tunnel({
|
||||
proxy: {
|
||||
port: Number(proxy.port),
|
||||
|
||||
@@ -98,7 +98,7 @@ function extractChannel (channel) {
|
||||
} else {
|
||||
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
|
||||
}
|
||||
return this.toColourspace('b-w');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,11 @@ const colourspace = {
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameter
|
||||
@@ -37,6 +42,10 @@ function tint (rgb) {
|
||||
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
|
||||
* which will produce an output image containing one color channel.
|
||||
* 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]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,9 @@ const blend = {
|
||||
* The images to composite must be the same size or smaller than the processed image.
|
||||
* If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||
*
|
||||
* Any resize or rotate operations in the same processing pipeline
|
||||
* will always be applied to the input image before composition.
|
||||
*
|
||||
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||
* `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||
@@ -50,12 +53,27 @@ const blend = {
|
||||
* `hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||
*
|
||||
* More information about blend modes can be found at
|
||||
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||
* https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
|
||||
* and https://www.cairographics.org/operators/
|
||||
*
|
||||
* @since 0.22.0
|
||||
*
|
||||
* @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')
|
||||
* .rotate(180)
|
||||
* .resize(300)
|
||||
@@ -78,6 +96,17 @@ const blend = {
|
||||
* @param {Number} [images[].input.create.height]
|
||||
* @param {Number} [images[].input.create.channels] - 3-4
|
||||
* @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||
* @param {Object} [images[].input.text] - describes a new text image to be created.
|
||||
* @param {string} [images[].input.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||
* @param {string} [images[].input.text.font] - font name to render with.
|
||||
* @param {string} [images[].input.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
|
||||
* @param {number} [images[].input.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
|
||||
* @param {number} [images[].input.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
|
||||
* @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
|
||||
* @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
|
||||
* @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
||||
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
|
||||
* @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
||||
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
|
||||
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
|
||||
* @param {Number} [images[].top] - the pixel offset from the top edge.
|
||||
@@ -89,7 +118,8 @@ const blend = {
|
||||
* @param {Number} [images[].raw.width]
|
||||
* @param {Number} [images[].raw.height]
|
||||
* @param {Number} [images[].raw.channels]
|
||||
* @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
|
||||
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
@@ -162,7 +192,6 @@ function composite (images) {
|
||||
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
|
||||
}
|
||||
}
|
||||
|
||||
return composite;
|
||||
});
|
||||
return this;
|
||||
|
||||
@@ -92,18 +92,38 @@ const debuglog = util.debuglog('sharp');
|
||||
* }
|
||||
* }).toFile('noise.png');
|
||||
*
|
||||
* @example
|
||||
* // Generate an image from text
|
||||
* await sharp({
|
||||
* text: {
|
||||
* text: 'Hello, world!',
|
||||
* width: 400, // max width
|
||||
* height: 300 // max height
|
||||
* }
|
||||
* }).toFile('text_bw.png');
|
||||
*
|
||||
* @example
|
||||
* // Generate an rgba image from text using pango markup and font
|
||||
* await sharp({
|
||||
* text: {
|
||||
* text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
|
||||
* font: 'sans',
|
||||
* rgba: true,
|
||||
* dpi: 300
|
||||
* }
|
||||
* }).toFile('text_rgba.png');
|
||||
*
|
||||
* @param {(Buffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
|
||||
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
|
||||
* a TypedArray containing raw pixel image data, or
|
||||
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
||||
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
||||
* @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort.
|
||||
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
|
||||
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
|
||||
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
||||
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG).
|
||||
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
|
||||
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
||||
* This can reduce memory usage and might improve performance on some systems.
|
||||
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
||||
@@ -127,6 +147,17 @@ const debuglog = util.debuglog('sharp');
|
||||
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
|
||||
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
|
||||
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
|
||||
* @param {Object} [options.text] - describes a new text image to be created.
|
||||
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||
* @param {string} [options.text.font] - font name to render with.
|
||||
* @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
|
||||
* @param {number} [options.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
|
||||
* @param {number} [options.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
|
||||
* @param {string} [options.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
|
||||
* @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
|
||||
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
||||
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
|
||||
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@@ -186,10 +217,14 @@ const Sharp = function (input, options) {
|
||||
medianSize: 0,
|
||||
blurSigma: 0,
|
||||
sharpenSigma: 0,
|
||||
sharpenFlat: 1,
|
||||
sharpenJagged: 2,
|
||||
sharpenM1: 1,
|
||||
sharpenM2: 2,
|
||||
sharpenX1: 2,
|
||||
sharpenY2: 10,
|
||||
sharpenY3: 20,
|
||||
threshold: 0,
|
||||
thresholdGrayscale: true,
|
||||
trimBackground: [],
|
||||
trimThreshold: 0,
|
||||
gamma: 0,
|
||||
gammaOut: 0,
|
||||
@@ -249,9 +284,14 @@ const Sharp = function (input, options) {
|
||||
webpNearLossless: false,
|
||||
webpSmartSubsample: false,
|
||||
webpEffort: 4,
|
||||
webpMinSize: false,
|
||||
webpMixed: false,
|
||||
gifBitdepth: 8,
|
||||
gifEffort: 7,
|
||||
gifDither: 1,
|
||||
gifInterFrameMaxError: 0,
|
||||
gifInterPaletteMaxError: 3,
|
||||
gifReoptimise: false,
|
||||
tiffQuality: 80,
|
||||
tiffCompression: 'jpeg',
|
||||
tiffPredictor: 'horizontal',
|
||||
@@ -268,6 +308,10 @@ const Sharp = function (input, options) {
|
||||
heifCompression: 'av1',
|
||||
heifEffort: 4,
|
||||
heifChromaSubsampling: '4:4:4',
|
||||
jxlDistance: 1,
|
||||
jxlDecodingTier: 0,
|
||||
jxlEffort: 7,
|
||||
jxlLossless: false,
|
||||
rawDepth: 'uchar',
|
||||
tileSize: 256,
|
||||
tileOverlap: 0,
|
||||
@@ -280,9 +324,10 @@ const Sharp = function (input, options) {
|
||||
tileBackground: [255, 255, 255, 255],
|
||||
tileCentre: false,
|
||||
tileId: 'https://example.com/iiif',
|
||||
tileBasename: '',
|
||||
timeoutSeconds: 0,
|
||||
linearA: 1,
|
||||
linearB: 0,
|
||||
linearA: [],
|
||||
linearB: [],
|
||||
// Function to notify of libvips warnings
|
||||
debuglog: warning => {
|
||||
this.emit('warning', warning);
|
||||
@@ -317,9 +362,7 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
|
||||
* // Using Promises to know when the pipeline is complete
|
||||
* const fs = require("fs");
|
||||
* const got = require("got");
|
||||
* const sharpStream = sharp({
|
||||
* failOnError: false
|
||||
* });
|
||||
* const sharpStream = sharp({ failOn: 'none' });
|
||||
*
|
||||
* const promises = [];
|
||||
*
|
||||
@@ -346,7 +389,7 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
|
||||
* .toFile("optimized-500.webp")
|
||||
* );
|
||||
*
|
||||
* // https://github.com/sindresorhus/got#gotstreamurl-options
|
||||
* // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
|
||||
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
|
||||
*
|
||||
* Promise.all(promises)
|
||||
|
||||
152
lib/input.js
@@ -4,14 +4,26 @@ const color = require('color');
|
||||
const is = require('./is');
|
||||
const sharp = require('./sharp');
|
||||
|
||||
/**
|
||||
* Justication alignment
|
||||
* @member
|
||||
* @private
|
||||
*/
|
||||
const align = {
|
||||
left: 'low',
|
||||
center: 'centre',
|
||||
centre: 'centre',
|
||||
right: 'high'
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract input options, if any, from an object.
|
||||
* @private
|
||||
*/
|
||||
function _inputOptionsFromObject (obj) {
|
||||
const { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
|
||||
return [raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
|
||||
? { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd }
|
||||
const { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
|
||||
return [raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
|
||||
? { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -21,7 +33,7 @@ function _inputOptionsFromObject (obj) {
|
||||
*/
|
||||
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
const inputDescriptor = {
|
||||
failOnError: true,
|
||||
failOn: 'warning',
|
||||
limitInputPixels: Math.pow(0x3FFF, 2),
|
||||
unlimited: false,
|
||||
sequentialRead: false
|
||||
@@ -39,7 +51,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
if (input.length === 0) {
|
||||
throw Error('Input Bit Array is empty');
|
||||
}
|
||||
inputDescriptor.buffer = Buffer.from(input.buffer);
|
||||
inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
||||
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
||||
// Plain Object descriptor, e.g. create
|
||||
inputOptions = input;
|
||||
@@ -56,14 +68,22 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
}`);
|
||||
}
|
||||
if (is.object(inputOptions)) {
|
||||
// Fail on error
|
||||
// Deprecated: failOnError
|
||||
if (is.defined(inputOptions.failOnError)) {
|
||||
if (is.bool(inputOptions.failOnError)) {
|
||||
inputDescriptor.failOnError = inputOptions.failOnError;
|
||||
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
|
||||
} else {
|
||||
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
|
||||
}
|
||||
}
|
||||
// failOn
|
||||
if (is.defined(inputOptions.failOn)) {
|
||||
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
|
||||
inputDescriptor.failOn = inputOptions.failOn;
|
||||
} else {
|
||||
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
|
||||
}
|
||||
}
|
||||
// Density
|
||||
if (is.defined(inputOptions.density)) {
|
||||
if (is.inRange(inputOptions.density, 1, 100000)) {
|
||||
@@ -78,10 +98,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
|
||||
? Math.pow(0x3FFF, 2)
|
||||
: 0;
|
||||
} else if (is.integer(inputOptions.limitInputPixels) && inputOptions.limitInputPixels >= 0) {
|
||||
} else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
|
||||
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
|
||||
} else {
|
||||
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
|
||||
throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
|
||||
}
|
||||
}
|
||||
// unlimited
|
||||
@@ -237,6 +257,81 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
throw new Error('Expected valid width, height and channels to create a new input image');
|
||||
}
|
||||
}
|
||||
// Create a new image with text
|
||||
if (is.defined(inputOptions.text)) {
|
||||
if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
|
||||
inputDescriptor.textValue = inputOptions.text.text;
|
||||
if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
|
||||
throw new Error('Expected only one of dpi or height');
|
||||
}
|
||||
if (is.defined(inputOptions.text.font)) {
|
||||
if (is.string(inputOptions.text.font)) {
|
||||
inputDescriptor.textFont = inputOptions.text.font;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.fontfile)) {
|
||||
if (is.string(inputOptions.text.fontfile)) {
|
||||
inputDescriptor.textFontfile = inputOptions.text.fontfile;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.width)) {
|
||||
if (is.number(inputOptions.text.width)) {
|
||||
inputDescriptor.textWidth = inputOptions.text.width;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.height)) {
|
||||
if (is.number(inputOptions.text.height)) {
|
||||
inputDescriptor.textHeight = inputOptions.text.height;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.align)) {
|
||||
if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
|
||||
inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
|
||||
} else {
|
||||
throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.justify)) {
|
||||
if (is.bool(inputOptions.text.justify)) {
|
||||
inputDescriptor.textJustify = inputOptions.text.justify;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.dpi)) {
|
||||
if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
|
||||
inputDescriptor.textDpi = inputOptions.text.dpi;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.rgba)) {
|
||||
if (is.bool(inputOptions.text.rgba)) {
|
||||
inputDescriptor.textRgba = inputOptions.text.rgba;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
|
||||
}
|
||||
}
|
||||
if (is.defined(inputOptions.text.spacing)) {
|
||||
if (is.number(inputOptions.text.spacing)) {
|
||||
inputDescriptor.textSpacing = inputOptions.text.spacing;
|
||||
} else {
|
||||
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
|
||||
}
|
||||
}
|
||||
delete inputDescriptor.buffer;
|
||||
} else {
|
||||
throw new Error('Expected a valid string to create an image with text.');
|
||||
}
|
||||
}
|
||||
} else if (is.defined(inputOptions)) {
|
||||
throw new Error('Invalid input options ' + inputOptions);
|
||||
}
|
||||
@@ -295,15 +390,18 @@ function _isStreamInput () {
|
||||
* This is taken from the header of the input image.
|
||||
* It does not include operations, such as resize, to be applied to the output image.
|
||||
*
|
||||
* Dimensions in the response will respect the `page` and `pages` properties of the
|
||||
* {@link /api-constructor#parameters|constructor parameters}.
|
||||
*
|
||||
* 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`
|
||||
* - `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)
|
||||
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
|
||||
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
|
||||
* - `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, see example below)
|
||||
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
|
||||
* - `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://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
|
||||
* - `density`: Number of pixels per inch (DPI), if present
|
||||
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
@@ -343,6 +441,17 @@ function _isStreamInput () {
|
||||
* // 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)`
|
||||
* @returns {Promise<Object>|Sharp}
|
||||
*/
|
||||
@@ -360,7 +469,7 @@ function metadata (callback) {
|
||||
} else {
|
||||
if (this._isStreamInput()) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.on('finish', () => {
|
||||
const finished = () => {
|
||||
this._flattenBufferIn();
|
||||
sharp.metadata(this.options, (err, metadata) => {
|
||||
if (err) {
|
||||
@@ -369,7 +478,12 @@ function metadata (callback) {
|
||||
resolve(metadata);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
if (this.writableFinished) {
|
||||
finished();
|
||||
} else {
|
||||
this.once('finish', finished);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -401,9 +515,9 @@ function metadata (callback) {
|
||||
* - `maxX` (x-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.
|
||||
* - `entropy`: Histogram-based estimation of greyscale entropy, 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 (experimental)
|
||||
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (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.
|
||||
* - `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
|
||||
* written to a buffer in order to run `stats` on the result (see third example).
|
||||
@@ -485,4 +599,6 @@ module.exports = function (Sharp) {
|
||||
metadata,
|
||||
stats
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.align = align;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,13 @@ const isRosetta = function () {
|
||||
|
||||
const globalLibvipsVersion = function () {
|
||||
if (process.platform !== 'win32') {
|
||||
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
|
||||
const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
|
||||
...spawnSyncOptions,
|
||||
env: {
|
||||
...env,
|
||||
PKG_CONFIG_PATH: pkgConfigPath()
|
||||
}
|
||||
}).stdout;
|
||||
/* istanbul ignore next */
|
||||
return (globalLibvipsVersion || '').trim();
|
||||
} else {
|
||||
@@ -83,12 +89,21 @@ const removeVendoredLibvips = function () {
|
||||
rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
const pkgConfigPath = function () {
|
||||
if (process.platform !== 'win32') {
|
||||
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']
|
||||
.filter(function (p) { return !!p; })
|
||||
.join(':');
|
||||
const brewPkgConfigPath = spawnSync(
|
||||
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
|
||||
spawnSyncOptions
|
||||
).stdout || '';
|
||||
return [
|
||||
brewPkgConfigPath.trim(),
|
||||
env.PKG_CONFIG_PATH,
|
||||
'/usr/local/lib/pkgconfig',
|
||||
'/usr/lib/pkgconfig',
|
||||
'/usr/local/libdata/pkgconfig',
|
||||
'/usr/libdata/pkgconfig'
|
||||
].filter(Boolean).join(':');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
250
lib/operation.js
@@ -18,8 +18,11 @@ const is = require('./is');
|
||||
*
|
||||
* The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
|
||||
*
|
||||
* Method order is important when both rotating and extracting regions,
|
||||
* for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||
* Only one rotation can occur per pipeline.
|
||||
* Previous calls to `rotate` in the same pipeline will be ignored.
|
||||
*
|
||||
* Method order is important when rotating, resizing and/or extracting regions,
|
||||
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||
*
|
||||
* @example
|
||||
* const pipeline = sharp()
|
||||
@@ -32,6 +35,16 @@ const is = require('./is');
|
||||
* });
|
||||
* readableStream.pipe(pipeline);
|
||||
*
|
||||
* @example
|
||||
* const rotateThenResize = await sharp(input)
|
||||
* .rotate(90)
|
||||
* .resize({ width: 16, height: 8, fit: 'fill' })
|
||||
* .toBuffer();
|
||||
* const resizeThenRotate = await sharp(input)
|
||||
* .resize({ width: 16, height: 8, fit: 'fill' })
|
||||
* .rotate(90)
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {number} [angle=auto] angle of rotation.
|
||||
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||
* @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||
@@ -39,6 +52,9 @@ const is = require('./is');
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function rotate (angle, options) {
|
||||
if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) {
|
||||
this.options.debuglog('ignoring previous rotate options');
|
||||
}
|
||||
if (!is.defined(angle)) {
|
||||
this.options.useExifOrientation = true;
|
||||
} else if (is.integer(angle) && !(angle % 90)) {
|
||||
@@ -61,8 +77,12 @@ 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 before rotation, 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]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
@@ -72,8 +92,12 @@ 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 before rotation, 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]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
@@ -181,44 +205,113 @@ function affine (matrix, options) {
|
||||
|
||||
/**
|
||||
* Sharpen the image.
|
||||
* 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.
|
||||
* 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`.
|
||||
* @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.
|
||||
* 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.
|
||||
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
|
||||
*
|
||||
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
|
||||
*
|
||||
* @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
|
||||
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10000
|
||||
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
|
||||
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
|
||||
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
|
||||
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
|
||||
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
|
||||
* @param {number} [flat] - (deprecated) see `options.m1`.
|
||||
* @param {number} [jagged] - (deprecated) see `options.m2`.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function sharpen (sigma, flat, jagged) {
|
||||
if (!is.defined(sigma)) {
|
||||
function sharpen (options, flat, jagged) {
|
||||
if (!is.defined(options)) {
|
||||
// No arguments: default to mild sharpen
|
||||
this.options.sharpenSigma = -1;
|
||||
} else if (is.bool(sigma)) {
|
||||
// Boolean argument: apply mild sharpen?
|
||||
this.options.sharpenSigma = sigma ? -1 : 0;
|
||||
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
|
||||
// Numeric argument: specific sigma
|
||||
this.options.sharpenSigma = sigma;
|
||||
// Control over flat areas
|
||||
} else if (is.bool(options)) {
|
||||
// Deprecated boolean argument: apply mild sharpen?
|
||||
this.options.sharpenSigma = options ? -1 : 0;
|
||||
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
|
||||
// Deprecated numeric argument: specific sigma
|
||||
this.options.sharpenSigma = options;
|
||||
// Deprecated control over flat areas
|
||||
if (is.defined(flat)) {
|
||||
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
||||
this.options.sharpenFlat = flat;
|
||||
this.options.sharpenM1 = flat;
|
||||
} else {
|
||||
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.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
||||
this.options.sharpenJagged = jagged;
|
||||
this.options.sharpenM2 = jagged;
|
||||
} else {
|
||||
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.000001, 10000)) {
|
||||
this.options.sharpenSigma = options.sigma;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10000', options.sigma);
|
||||
}
|
||||
if (is.defined(options.m1)) {
|
||||
if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
|
||||
this.options.sharpenM1 = options.m1;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.m2)) {
|
||||
if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
|
||||
this.options.sharpenM2 = options.m2;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.x1)) {
|
||||
if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
|
||||
this.options.sharpenX1 = options.x1;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.y2)) {
|
||||
if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
|
||||
this.options.sharpenY2 = options.y2;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.y3)) {
|
||||
if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
|
||||
this.options.sharpenY3 = options.y3;
|
||||
} else {
|
||||
throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
|
||||
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -226,6 +319,13 @@ function sharpen (sigma, flat, jagged) {
|
||||
/**
|
||||
* Apply median filter.
|
||||
* 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
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
@@ -338,6 +438,17 @@ function gamma (gamma, gammaOut) {
|
||||
|
||||
/**
|
||||
* Produce the "negative" of the image.
|
||||
*
|
||||
* @example
|
||||
* const output = await sharp(input)
|
||||
* .negate()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* const output = await sharp(input)
|
||||
* .negate({ alpha: false })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
|
||||
* @returns {Sharp}
|
||||
@@ -356,6 +467,10 @@ function negate (options) {
|
||||
|
||||
/**
|
||||
* 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]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
@@ -366,6 +481,10 @@ function normalise (normalise) {
|
||||
|
||||
/**
|
||||
* Alternative spelling of normalise.
|
||||
*
|
||||
* @example
|
||||
* const output = await sharp(input).normalize().toBuffer();
|
||||
*
|
||||
* @param {Boolean} [normalize=true]
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
@@ -381,6 +500,14 @@ function normalize (normalize) {
|
||||
*
|
||||
* @since 0.28.3
|
||||
*
|
||||
* @example
|
||||
* const output = await sharp(input)
|
||||
* .clahe({
|
||||
* width: 3,
|
||||
* height: 3,
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {number} options.width - integer width of the region in pixels.
|
||||
* @param {number} options.height - integer height of the region in pixels.
|
||||
@@ -519,26 +646,55 @@ function boolean (operand, operator, options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the linear formula a * input + b to the image (levels adjustment)
|
||||
* @param {number} [a=1.0] multiplier
|
||||
* @param {number} [b=0.0] offset
|
||||
* Apply the linear formula `a` * input + `b` to the image to adjust image levels.
|
||||
*
|
||||
* When a single number is provided, it will be used for all image channels.
|
||||
* When an array of numbers is provided, the array length must match the number of channels.
|
||||
*
|
||||
* @example
|
||||
* await sharp(input)
|
||||
* .linear(0.5, 2)
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* await sharp(rgbInput)
|
||||
* .linear(
|
||||
* [0.25, 0.5, 0.75],
|
||||
* [150, 100, 50]
|
||||
* )
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {(number|number[])} [a=[]] multiplier
|
||||
* @param {(number|number[])} [b=[]] offset
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function linear (a, b) {
|
||||
if (!is.defined(a) && is.number(b)) {
|
||||
a = 1.0;
|
||||
} else if (is.number(a) && !is.defined(b)) {
|
||||
b = 0.0;
|
||||
}
|
||||
if (!is.defined(a)) {
|
||||
this.options.linearA = 1.0;
|
||||
this.options.linearA = [];
|
||||
} else if (is.number(a)) {
|
||||
this.options.linearA = [a];
|
||||
} else if (Array.isArray(a) && a.length && a.every(is.number)) {
|
||||
this.options.linearA = a;
|
||||
} else {
|
||||
throw is.invalidParameterError('a', 'numeric', a);
|
||||
throw is.invalidParameterError('a', 'number or array of numbers', a);
|
||||
}
|
||||
if (!is.defined(b)) {
|
||||
this.options.linearB = 0.0;
|
||||
this.options.linearB = [];
|
||||
} else if (is.number(b)) {
|
||||
this.options.linearB = [b];
|
||||
} else if (Array.isArray(b) && b.length && b.every(is.number)) {
|
||||
this.options.linearB = b;
|
||||
} else {
|
||||
throw is.invalidParameterError('b', 'numeric', b);
|
||||
throw is.invalidParameterError('b', 'number or array of numbers', b);
|
||||
}
|
||||
if (this.options.linearA.length !== this.options.linearB.length) {
|
||||
throw new Error('Expected a and b to be arrays of the same length');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -590,28 +746,38 @@ function recomb (inputMatrix) {
|
||||
* @since 0.22.1
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* // increase brightness by a factor of 2
|
||||
* const output = await sharp(input)
|
||||
* .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({
|
||||
* hue: 180 // hue-rotate by 180 degrees
|
||||
* });
|
||||
* hue: 180
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* sharp(input)
|
||||
* @example
|
||||
* // increase lightness by +50
|
||||
* const output = await sharp(input)
|
||||
* .modulate({
|
||||
* lightness: 50 // increase lightness by +50
|
||||
* });
|
||||
* lightness: 50
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||
* sharp(input)
|
||||
* const output = await sharp(input)
|
||||
* .modulate({
|
||||
* brightness: 0.5,
|
||||
* saturation: 0.5,
|
||||
* hue: 90
|
||||
* });
|
||||
* hue: 90,
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.brightness] Brightness multiplier
|
||||
|
||||
220
lib/output.js
@@ -10,6 +10,9 @@ const formats = new Map([
|
||||
['avif', 'avif'],
|
||||
['jpeg', 'jpeg'],
|
||||
['jpg', 'jpeg'],
|
||||
['jpe', 'jpeg'],
|
||||
['tile', 'tile'],
|
||||
['dz', 'tile'],
|
||||
['png', 'png'],
|
||||
['raw', 'raw'],
|
||||
['tiff', 'tiff'],
|
||||
@@ -19,10 +22,13 @@ const formats = new Map([
|
||||
['jp2', 'jp2'],
|
||||
['jpx', 'jp2'],
|
||||
['j2k', 'jp2'],
|
||||
['j2c', 'jp2']
|
||||
['j2c', 'jp2'],
|
||||
['jxl', 'jxl']
|
||||
]);
|
||||
|
||||
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
||||
const jp2Regex = /\.jp[2x]|j2[kc]$/i;
|
||||
|
||||
const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
|
||||
|
||||
const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
||||
|
||||
@@ -55,6 +61,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
|
||||
* `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||
* @returns {Promise<Object>} - when no callback is provided
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@@ -64,6 +71,8 @@ function toFile (fileOut, callback) {
|
||||
err = new Error('Missing output file path');
|
||||
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
||||
err = new Error('Cannot use same file for input and output');
|
||||
} else if (jp2Regex.test(fileOut) && !this.constructor.format.jp2k.output.file) {
|
||||
err = errJp2Save();
|
||||
}
|
||||
if (err) {
|
||||
if (is.fn(callback)) {
|
||||
@@ -82,6 +91,8 @@ function toFile (fileOut, callback) {
|
||||
* Write output to a Buffer.
|
||||
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||
*
|
||||
* Use {@link toFormat} or one of the format-specific functions such as {@link jpeg}, {@link png} etc. to set the output format.
|
||||
*
|
||||
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
|
||||
*
|
||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||
@@ -93,6 +104,7 @@ function toFile (fileOut, callback) {
|
||||
* - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
||||
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||
*
|
||||
* A `Promise` is returned when `callback` is not provided.
|
||||
*
|
||||
@@ -108,6 +120,7 @@ function toFile (fileOut, callback) {
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .png()
|
||||
* .toBuffer({ resolveWithObject: true })
|
||||
* .then(({ data, info }) => { ... })
|
||||
* .catch(err => { ... });
|
||||
@@ -151,6 +164,8 @@ function toBuffer (options, callback) {
|
||||
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
*
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
*
|
||||
* @example
|
||||
* sharp('input.jpg')
|
||||
* .withMetadata()
|
||||
@@ -397,6 +412,14 @@ function png (options) {
|
||||
if (is.defined(options.adaptiveFiltering)) {
|
||||
this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
|
||||
}
|
||||
const colours = options.colours || options.colors;
|
||||
if (is.defined(colours)) {
|
||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||
this.options.pngBitdepth = bitdepthFromColourCount(colours);
|
||||
} else {
|
||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.palette)) {
|
||||
this._setBooleanOption('pngPalette', options.palette);
|
||||
} else if ([options.quality, options.effort, options.colours, options.colors, options.dither].some(is.defined)) {
|
||||
@@ -417,14 +440,6 @@ function png (options) {
|
||||
throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
|
||||
}
|
||||
}
|
||||
const colours = options.colours || options.colors;
|
||||
if (is.defined(colours)) {
|
||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||
this.options.pngBitdepth = bitdepthFromColourCount(colours);
|
||||
} else {
|
||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.dither)) {
|
||||
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
|
||||
this.options.pngDither = options.dither;
|
||||
@@ -461,6 +476,8 @@ function png (options) {
|
||||
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
|
||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
|
||||
* @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
|
||||
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
|
||||
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
@@ -490,14 +507,19 @@ function webp (options) {
|
||||
if (is.defined(options.smartSubsample)) {
|
||||
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
|
||||
}
|
||||
const effort = options.effort || options.reductionEffort;
|
||||
if (is.defined(effort)) {
|
||||
if (is.integer(effort) && is.inRange(effort, 0, 6)) {
|
||||
this.options.webpEffort = effort;
|
||||
if (is.defined(options.effort)) {
|
||||
if (is.integer(options.effort) && is.inRange(options.effort, 0, 6)) {
|
||||
this.options.webpEffort = options.effort;
|
||||
} else {
|
||||
throw is.invalidParameterError('effort', 'integer between 0 and 6', effort);
|
||||
throw is.invalidParameterError('effort', 'integer between 0 and 6', options.effort);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.minSize)) {
|
||||
this._setBooleanOption('webpMinSize', options.minSize);
|
||||
}
|
||||
if (is.defined(options.mixed)) {
|
||||
this._setBooleanOption('webpMixed', options.mixed);
|
||||
}
|
||||
}
|
||||
trySetAnimationOptions(options, this.options);
|
||||
return this._updateFormatOut('webp', options);
|
||||
@@ -508,13 +530,15 @@ function webp (options) {
|
||||
*
|
||||
* The first entry in the palette is reserved for transparency.
|
||||
*
|
||||
* The palette of the input image will be re-used if possible.
|
||||
*
|
||||
* @since 0.30.0
|
||||
*
|
||||
* @example
|
||||
* // Convert PNG to GIF
|
||||
* await sharp(pngBuffer)
|
||||
* .gif()
|
||||
* .toBuffer());
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // Convert animated WebP to animated GIF
|
||||
@@ -528,11 +552,21 @@ function webp (options) {
|
||||
* .gif({ dither: 0 })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // Lossy file size reduction of animated GIF
|
||||
* await sharp('in.gif', { animated: true })
|
||||
* .gif({ interFrameMaxError: 8 })
|
||||
* .toFile('optim.gif');
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {boolean} [options.reoptimise=false] - always generate new palettes (slow), re-use existing by default
|
||||
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
|
||||
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
|
||||
* @param {number} [options.colors=256] - alternative spelling of `options.colours`
|
||||
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
|
||||
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
|
||||
* @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
|
||||
* @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
|
||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
|
||||
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
|
||||
@@ -541,6 +575,11 @@ function webp (options) {
|
||||
*/
|
||||
function gif (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.reoptimise)) {
|
||||
this._setBooleanOption('gifReoptimise', options.reoptimise);
|
||||
} else if (is.defined(options.reoptimize)) {
|
||||
this._setBooleanOption('gifReoptimise', options.reoptimize);
|
||||
}
|
||||
const colours = options.colours || options.colors;
|
||||
if (is.defined(colours)) {
|
||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||
@@ -563,6 +602,20 @@ function gif (options) {
|
||||
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.interFrameMaxError)) {
|
||||
if (is.number(options.interFrameMaxError) && is.inRange(options.interFrameMaxError, 0, 32)) {
|
||||
this.options.gifInterFrameMaxError = options.interFrameMaxError;
|
||||
} else {
|
||||
throw is.invalidParameterError('interFrameMaxError', 'number between 0.0 and 32.0', options.interFrameMaxError);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.interPaletteMaxError)) {
|
||||
if (is.number(options.interPaletteMaxError) && is.inRange(options.interPaletteMaxError, 0, 256)) {
|
||||
this.options.gifInterPaletteMaxError = options.interPaletteMaxError;
|
||||
} else {
|
||||
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
|
||||
}
|
||||
}
|
||||
}
|
||||
trySetAnimationOptions(options, this.options);
|
||||
return this._updateFormatOut('gif', options);
|
||||
@@ -604,7 +657,7 @@ function gif (options) {
|
||||
/* istanbul ignore next */
|
||||
function jp2 (options) {
|
||||
if (!this.constructor.format.jp2k.output.buffer) {
|
||||
throw errJp2Save;
|
||||
throw errJp2Save();
|
||||
}
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
@@ -697,7 +750,7 @@ function trySetAnimationOptions (source, target) {
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
|
||||
* @param {string} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
|
||||
* @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k
|
||||
* @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
|
||||
* @param {boolean} [options.pyramid=false] - write an image pyramid
|
||||
* @param {boolean} [options.tile=false] - write a tiled tiff
|
||||
@@ -765,10 +818,10 @@ function tiff (options) {
|
||||
}
|
||||
// compression
|
||||
if (is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['none', 'jpeg', 'deflate', 'packbits', 'ccittfax4', 'lzw', 'webp', 'zstd', 'jp2k'])) {
|
||||
this.options.tiffCompression = options.compression;
|
||||
} else {
|
||||
throw is.invalidParameterError('compression', 'one of: lzw, deflate, jpeg, ccittfax4, none', options.compression);
|
||||
throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression);
|
||||
}
|
||||
}
|
||||
// predictor
|
||||
@@ -799,6 +852,16 @@ function tiff (options) {
|
||||
*
|
||||
* AVIF image sequences are not supported.
|
||||
*
|
||||
* @example
|
||||
* const data = await sharp(input)
|
||||
* .avif({ effort: 2 })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* const data = await sharp(input)
|
||||
* .avif({ lossless: true })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @since 0.27.0
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
@@ -816,9 +879,14 @@ function avif (options) {
|
||||
/**
|
||||
* Use these HEIF options for output image.
|
||||
*
|
||||
* Support for patent-encumbered HEIC images requires the use of a
|
||||
* Support for patent-encumbered HEIC images using `hevc` compression requires the use of a
|
||||
* globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||
*
|
||||
* @example
|
||||
* const data = await sharp(input)
|
||||
* .heif({ compression: 'hevc' })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @since 0.23.0
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
@@ -859,12 +927,6 @@ function heif (options) {
|
||||
} else {
|
||||
throw is.invalidParameterError('effort', 'integer between 0 and 9', options.effort);
|
||||
}
|
||||
} else if (is.defined(options.speed)) {
|
||||
if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
|
||||
this.options.heifEffort = 9 - options.speed;
|
||||
} else {
|
||||
throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.chromaSubsampling)) {
|
||||
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||
@@ -877,6 +939,71 @@ function heif (options) {
|
||||
return this._updateFormatOut('heif', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these JPEG-XL (JXL) options for output image.
|
||||
*
|
||||
* This feature is experimental, please do not use in production systems.
|
||||
*
|
||||
* Requires libvips compiled with support for libjxl.
|
||||
* The prebuilt binaries do not include this - see
|
||||
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
||||
*
|
||||
* Image metadata (EXIF, XMP) is unsupported.
|
||||
*
|
||||
* @since 0.31.3
|
||||
*
|
||||
* @param {Object} [options] - output options
|
||||
* @param {number} [options.distance=1.0] - maximum encoding error, between 0 (highest quality) and 15 (lowest quality)
|
||||
* @param {number} [options.quality] - calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified
|
||||
* @param {number} [options.decodingTier=0] - target decode speed tier, between 0 (highest quality) and 4 (lowest quality)
|
||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||
* @param {number} [options.effort=7] - CPU effort, between 3 (fastest) and 9 (slowest)
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function jxl (options) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
// https://github.com/libjxl/libjxl/blob/0aeea7f180bafd6893c1db8072dcb67d2aa5b03d/tools/cjxl_main.cc#L640-L644
|
||||
this.options.jxlDistance = options.quality >= 30
|
||||
? 0.1 + (100 - options.quality) * 0.09
|
||||
: 53 / 3000 * options.quality * options.quality - 23 / 20 * options.quality + 25;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||
}
|
||||
} else if (is.defined(options.distance)) {
|
||||
if (is.number(options.distance) && is.inRange(options.distance, 0, 15)) {
|
||||
this.options.jxlDistance = options.distance;
|
||||
} else {
|
||||
throw is.invalidParameterError('distance', 'number between 0.0 and 15.0', options.distance);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.decodingTier)) {
|
||||
if (is.integer(options.decodingTier) && is.inRange(options.decodingTier, 0, 4)) {
|
||||
this.options.jxlDecodingTier = options.decodingTier;
|
||||
} else {
|
||||
throw is.invalidParameterError('decodingTier', 'integer between 0 and 4', options.decodingTier);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.lossless)) {
|
||||
if (is.bool(options.lossless)) {
|
||||
this.options.jxlLossless = options.lossless;
|
||||
} else {
|
||||
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.effort)) {
|
||||
if (is.integer(options.effort) && is.inRange(options.effort, 3, 9)) {
|
||||
this.options.jxlEffort = options.effort;
|
||||
} else {
|
||||
throw is.invalidParameterError('effort', 'integer between 3 and 9', options.effort);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('jxl', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force output to be raw, uncompressed pixel data.
|
||||
* Pixel ordering is left-to-right, top-to-bottom, without padding.
|
||||
@@ -918,9 +1045,12 @@ function raw (options) {
|
||||
|
||||
/**
|
||||
* Use tile-based deep zoom (image pyramid) output.
|
||||
*
|
||||
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
||||
* Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
||||
*
|
||||
* The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
||||
*
|
||||
* @example
|
||||
* sharp('input.tiff')
|
||||
* .png()
|
||||
@@ -932,6 +1062,17 @@ function raw (options) {
|
||||
* // output_files contains 512x512 tiles grouped by zoom level
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* const zipFileWithTiles = await sharp(input)
|
||||
* .tile({ basename: "tiles" })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* const iiififier = sharp().tile({ layout: "iiif" });
|
||||
* readableStream
|
||||
* .pipe(iiififier)
|
||||
* .pipe(writeableStream);
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
||||
* @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
||||
@@ -944,6 +1085,7 @@ function raw (options) {
|
||||
* @param {boolean} [options.centre=false] centre image in tile.
|
||||
* @param {boolean} [options.center=false] alternative spelling of centre.
|
||||
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
|
||||
* @param {string} [options.basename] the name of the directory within the zip file when container is `zip`.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@@ -1025,6 +1167,14 @@ function tile (options) {
|
||||
throw is.invalidParameterError('id', 'string', options.id);
|
||||
}
|
||||
}
|
||||
// Basename for zip container
|
||||
if (is.defined(options.basename)) {
|
||||
if (is.string(options.basename)) {
|
||||
this.options.tileBasename = options.basename;
|
||||
} else {
|
||||
throw is.invalidParameterError('basename', 'string', options.basename);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Format
|
||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||
@@ -1042,6 +1192,17 @@ function tile (options) {
|
||||
* The clock starts when libvips opens an input image for processing.
|
||||
* Time spent waiting for a libuv thread to become available is not included.
|
||||
*
|
||||
* @example
|
||||
* // Ensure processing takes no longer than 3 seconds
|
||||
* try {
|
||||
* const data = await sharp(input)
|
||||
* .blur(1000)
|
||||
* .timeout({ seconds: 3 })
|
||||
* .toBuffer();
|
||||
* } catch (err) {
|
||||
* if (err.message.includes('timeout')) { ... }
|
||||
* }
|
||||
*
|
||||
* @since 0.29.2
|
||||
*
|
||||
* @param {Object} options
|
||||
@@ -1136,7 +1297,7 @@ function _pipeline (callback) {
|
||||
this.push(data);
|
||||
}
|
||||
this.push(null);
|
||||
this.emit('close');
|
||||
this.on('end', () => this.emit('close'));
|
||||
});
|
||||
});
|
||||
if (this.streamInFinished) {
|
||||
@@ -1152,7 +1313,7 @@ function _pipeline (callback) {
|
||||
this.push(data);
|
||||
}
|
||||
this.push(null);
|
||||
this.emit('close');
|
||||
this.on('end', () => this.emit('close'));
|
||||
});
|
||||
}
|
||||
return this;
|
||||
@@ -1213,6 +1374,7 @@ module.exports = function (Sharp) {
|
||||
tiff,
|
||||
avif,
|
||||
heif,
|
||||
jxl,
|
||||
gif,
|
||||
raw,
|
||||
tile,
|
||||
|
||||
@@ -7,10 +7,12 @@ const env = process.env;
|
||||
module.exports = function () {
|
||||
const arch = env.npm_config_arch || process.arch;
|
||||
const platform = env.npm_config_platform || process.platform;
|
||||
/* istanbul ignore next */
|
||||
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinuxSync()) ? detectLibc.familySync() : '';
|
||||
const libc = process.env.npm_config_libc ||
|
||||
/* istanbul ignore next */
|
||||
(detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '');
|
||||
const libcId = platform !== 'linux' || libc === detectLibc.GLIBC ? '' : libc;
|
||||
|
||||
const platformId = [`${platform}${libc}`];
|
||||
const platformId = [`${platform}${libcId}`];
|
||||
|
||||
if (arch === 'arm') {
|
||||
const fallback = process.versions.electron ? '7' : '6';
|
||||
|
||||
108
lib/resize.js
@@ -92,6 +92,13 @@ function isRotationExpected (options) {
|
||||
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function isResizeExpected (options) {
|
||||
return options.width !== -1 || options.height !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize image to `width`, `height` or `width x height`.
|
||||
*
|
||||
@@ -104,7 +111,9 @@ function isRotationExpected (options) {
|
||||
*
|
||||
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
||||
*
|
||||
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.png">
|
||||
*
|
||||
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
||||
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
|
||||
@@ -123,6 +132,9 @@ function isRotationExpected (options) {
|
||||
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
||||
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||
*
|
||||
* Only one resize can occur per pipeline.
|
||||
* Previous calls to `resize` in the same pipeline will be ignored.
|
||||
*
|
||||
* @example
|
||||
* sharp(input)
|
||||
* .resize({ width: 100 })
|
||||
@@ -207,11 +219,11 @@ function isRotationExpected (options) {
|
||||
* @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
* @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
|
||||
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
|
||||
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this takes priority.
|
||||
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this takes priority.
|
||||
* @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
||||
* @param {String} [options.position='centre'] - position, gravity or strategy to use when `fit` is `cover` or `contain`.
|
||||
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
||||
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
||||
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
|
||||
* @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
|
||||
* @param {Boolean} [options.withoutReduction=false] - do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option.
|
||||
@@ -220,6 +232,9 @@ function isRotationExpected (options) {
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function resize (width, height, options) {
|
||||
if (isResizeExpected(this.options)) {
|
||||
this.options.debuglog('ignoring previous resize options');
|
||||
}
|
||||
if (is.defined(width)) {
|
||||
if (is.object(width) && !is.defined(options)) {
|
||||
options = width;
|
||||
@@ -300,6 +315,9 @@ function resize (width, height, options) {
|
||||
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
|
||||
}
|
||||
}
|
||||
if (isRotationExpected(this.options) && isResizeExpected(this.options)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -412,7 +430,10 @@ function extend (extend) {
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function extract (options) {
|
||||
const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
||||
const suffix = isResizeExpected(this.options) || this.options.widthPre !== -1 ? 'Post' : 'Pre';
|
||||
if (this.options[`width${suffix}`] !== -1) {
|
||||
this.options.debuglog('ignoring previous extract options');
|
||||
}
|
||||
['left', 'top', 'width', 'height'].forEach(function (name) {
|
||||
const value = options[name];
|
||||
if (is.integer(value) && value >= 0) {
|
||||
@@ -422,32 +443,87 @@ function extract (options) {
|
||||
}
|
||||
}, this);
|
||||
// Ensure existing rotation occurs before pre-resize extraction
|
||||
if (suffix === 'Pre' && isRotationExpected(this.options)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
if (isRotationExpected(this.options) && !isResizeExpected(this.options)) {
|
||||
if (this.options.widthPre === -1 || this.options.widthPost === -1) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||
* Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||
*
|
||||
* Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
||||
*
|
||||
* If the result of this operation would trim an image to nothing then no change is made.
|
||||
*
|
||||
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
*
|
||||
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||
* @example
|
||||
* // Trim pixels with a colour similar to that of the top-left pixel.
|
||||
* sharp(input)
|
||||
* .trim()
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* @example
|
||||
* // Trim pixels with the exact same colour as that of the top-left pixel.
|
||||
* sharp(input)
|
||||
* .trim(0)
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* @example
|
||||
* // Trim only pixels with a similar colour to red.
|
||||
* sharp(input)
|
||||
* .trim("#FF0000")
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* @example
|
||||
* // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||
* sharp(input)
|
||||
* .trim({
|
||||
* background: "yellow",
|
||||
* threshold: 42,
|
||||
* })
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
|
||||
* @param {string|Object} [trim.background='top-left pixel'] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
|
||||
* @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function trim (threshold) {
|
||||
if (!is.defined(threshold)) {
|
||||
function trim (trim) {
|
||||
if (!is.defined(trim)) {
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(threshold) && threshold > 0) {
|
||||
this.options.trimThreshold = threshold;
|
||||
} else if (is.string(trim)) {
|
||||
this._setBackgroundColourOption('trimBackground', trim);
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(trim)) {
|
||||
if (trim >= 0) {
|
||||
this.options.trimThreshold = trim;
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||
}
|
||||
} else if (is.object(trim)) {
|
||||
this._setBackgroundColourOption('trimBackground', trim.background);
|
||||
if (!is.defined(trim.threshold)) {
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(trim.threshold) && trim.threshold >= 0) {
|
||||
this.options.trimThreshold = trim.threshold;
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
|
||||
throw is.invalidParameterError('trim', 'string, number or object', trim);
|
||||
}
|
||||
if (this.options.trimThreshold && isRotationExpected(this.options)) {
|
||||
if (isRotationExpected(this.options)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
return this;
|
||||
|
||||
@@ -12,8 +12,11 @@ try {
|
||||
help.push('- Update Homebrew: "brew update && brew upgrade vips"');
|
||||
} else {
|
||||
const [platform, arch] = platformAndArch.split('-');
|
||||
if (platform === 'linux' && /Module did not self-register/.test(err.message)) {
|
||||
help.push('- Using worker threads? See https://sharp.pixelplumbing.com/install#worker-threads');
|
||||
}
|
||||
help.push(
|
||||
'- Install with the --verbose flag and look for errors: "npm install --ignore-scripts=false --verbose sharp"',
|
||||
'- Install with verbose logging and look for errors: "npm install --ignore-scripts=false --foreground-scripts --verbose sharp"',
|
||||
`- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"`
|
||||
);
|
||||
}
|
||||
@@ -21,7 +24,7 @@ try {
|
||||
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
|
||||
);
|
||||
// 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));
|
||||
if (loadedModule) {
|
||||
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
|
||||
|
||||
@@ -17,6 +17,10 @@ const sharp = require('./sharp');
|
||||
* @returns {Object}
|
||||
*/
|
||||
const format = sharp.format();
|
||||
format.heif.output.alias = ['avif', 'heic'];
|
||||
format.jpeg.output.alias = ['jpe', 'jpg'];
|
||||
format.tiff.output.alias = ['tif'];
|
||||
format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
|
||||
|
||||
/**
|
||||
* An Object containing the available interpolators and their proper values
|
||||
@@ -103,7 +107,11 @@ cache(true);
|
||||
|
||||
/**
|
||||
* Gets or, when a concurrency is provided, sets
|
||||
* the number of threads _libvips'_ should create to process each image.
|
||||
* the maximum number of threads _libvips_ should use to process _each image_.
|
||||
* These are from a thread pool managed by glib,
|
||||
* which helps avoid the overhead of creating new threads.
|
||||
*
|
||||
* This method always returns the current concurrency.
|
||||
*
|
||||
* The default value is the number of CPU cores,
|
||||
* except when using glibc-based Linux without jemalloc,
|
||||
@@ -111,10 +119,19 @@ cache(true);
|
||||
*
|
||||
* A value of `0` will reset this to the number of CPU cores.
|
||||
*
|
||||
* The maximum number of images that can be processed in parallel
|
||||
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
* Some image format libraries spawn additional threads,
|
||||
* e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||
* and these are independent of the value set here.
|
||||
*
|
||||
* This method always returns the current concurrency.
|
||||
* The maximum number of images that sharp can process in parallel
|
||||
* is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
|
||||
* which defaults to 4.
|
||||
*
|
||||
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
|
||||
*
|
||||
* For example, by default, a machine with 8 CPU cores will process
|
||||
* 4 images in parallel and use up to 8 threads per image,
|
||||
* so there will be up to 32 concurrent threads.
|
||||
*
|
||||
* @example
|
||||
* const threads = sharp.concurrency(); // 4
|
||||
|
||||
63
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
||||
"version": "0.30.1",
|
||||
"version": "0.31.3",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -81,16 +81,19 @@
|
||||
"Taneli Vatanen <taneli.vatanen@gmail.com>",
|
||||
"Joris Dugué <zaruike10@gmail.com>",
|
||||
"Chris Banks <christopher.bradley.banks@gmail.com>",
|
||||
"Ompal Singh <ompal.hitm09@gmail.com>"
|
||||
"Ompal Singh <ompal.hitm09@gmail.com>",
|
||||
"Brodan <christopher.hranj@gmail.com",
|
||||
"Ankur Parihar <ankur.github@gmail.com>",
|
||||
"Brahim Ait elhaj <brahima@gmail.com>",
|
||||
"Mart Jansink <m.jansink@gmail.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
||||
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||
"test": "npm run test-lint && npm run test-unit && npm run test-licensing",
|
||||
"test-lint": "semistandard && cpplint",
|
||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
|
||||
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha",
|
||||
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||
"test-coverage": "./test/coverage/report.sh",
|
||||
"test-leak": "./test/leak/leak.sh",
|
||||
"docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
|
||||
"docs-serve": "cd docs && npx serve",
|
||||
@@ -126,58 +129,58 @@
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"color": "^4.2.0",
|
||||
"detect-libc": "^2.0.0",
|
||||
"node-addon-api": "^4.3.0",
|
||||
"prebuild-install": "^7.0.1",
|
||||
"semver": "^7.3.5",
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.1",
|
||||
"node-addon-api": "^5.0.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.3.8",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^2.1.1",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^3.2.3",
|
||||
"async": "^3.2.4",
|
||||
"cc": "^3.0.1",
|
||||
"decompress-zip": "^0.3.3",
|
||||
"documentation": "^13.2.5",
|
||||
"documentation": "^14.0.1",
|
||||
"exif-reader": "^1.0.3",
|
||||
"extract-zip": "^2.0.1",
|
||||
"icc": "^2.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^9.2.0",
|
||||
"mock-fs": "^5.1.2",
|
||||
"mocha": "^10.2.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prebuild": "^11.0.3",
|
||||
"prebuild": "^11.0.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"semistandard": "^16.0.1"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.12.2",
|
||||
"libvips": "8.13.3",
|
||||
"integrity": {
|
||||
"darwin-arm64v8": "sha512-p46s/bbJAjkOXzPISZt9HUpG9GWjwQkYnLLRLKzsBJHLtB3X6C6Y/zXI5Hd0DOojcFkks9a0kTN+uDQ/XJY19g==",
|
||||
"darwin-x64": "sha512-6vOHVZnvXwe6EXRsy29jdkUzBE6ElNpXUwd+m8vV7qy32AnXu7B9YemHsZ44vWviIwPZeXF6Nhd9EFLM0wWohw==",
|
||||
"linux-arm64v8": "sha512-XwZdS63yhqLtbFtx/0eoLF/Agf5qtTrI11FMnMRpuBJWd4jHB60RAH+uzYUgoChCmKIS+AeXYMLm4d8Ns2QX8w==",
|
||||
"linux-armv6": "sha512-Rh0Q0kqwPG2MjXWOkPCuPEyiUKFgKJYWLgS835D4MrXgdKr8Tft/eVrc2iGIxt2re30VpDiZ1h0Rby1aCZt2zw==",
|
||||
"linux-armv7": "sha512-heTS/MsmRvu4JljINxP+vDiS9ZZfuGhr3IStb5F7Gc0/QLRhllYAg4rcO8L1eTK9sIIzG5ARvI19+YUZe7WbzA==",
|
||||
"linux-x64": "sha512-SSWAwBFi0hx8V/h/v82tTFGKWTFv9FiCK3Timz5OExuI+sX1Ngrd0PVQaWXOThGNdel/fcD3Bz9YjSt4feBR1g==",
|
||||
"linuxmusl-arm64v8": "sha512-Rhks+5C7p7aO6AucLT1uvzo8ohlqcqCUPgZmN+LZjsPWob/Iix3MfiDYtv/+gTvdeEfXxbCU6/YuPBwHQ7/crA==",
|
||||
"linuxmusl-x64": "sha512-IOyjSQqpWVntqOUpCHVWuQwACwmmjdi15H8Pc+Ma1JkhPogTfVsFQWyL7DuOTD3Yr23EuYGzovUX00duOtfy/g==",
|
||||
"win32-arm64v8": "sha512-A+Qe8Ipewtvw9ldvF6nWed2J8kphzrUE04nFeKCtNx6pfGQ/MAlCKMjt/U8VgUKNjB01zJDUW9XE0+FhGZ/UpQ==",
|
||||
"win32-ia32": "sha512-cMrAvwFdDeAVnLJt0IPMPRKaIFhyXYGTprsM0DND9VUHE8F7dJMR44tS5YkXsGh1QNDtjKT6YuxAVUglmiXtpA==",
|
||||
"win32-x64": "sha512-vLFIfw6aW2zABa8jpgzWDhljnE6glktrddErVyazAIoHl6BFFe/Da+LK1DbXvIYHz7fyOoKhSfCJHCiJG1Vg6w=="
|
||||
"darwin-arm64v8": "sha512-xFgYt7CtQSZcWoyUdzPTDNHbioZIrZSEU+gkMxzH4Cgjhi4/N49UsonnIZhKQoTBGloAqEexHeMx4rYTQ2Kgvw==",
|
||||
"darwin-x64": "sha512-6SivWKzu15aUMMohe0wg7sNYMPETVnOe40BuWsnKOgzl3o5FpQqNSgs+68Mi8Za3Qti9/DaR+H/fyD0x48Af2w==",
|
||||
"linux-arm64v8": "sha512-b+iI9V/ehgDabXYRQcvqa5CEysh+1FQsgFmYc358StCrJCDahwNmsQdsiH1GOVd5WaWh5wHUGByPwMmFOO16Aw==",
|
||||
"linux-armv6": "sha512-zRP2F+EiustLE4bXSH8AHCxwfemh9d+QuvmPjira/HL6uJOUuA7SyQgVV1TPwTQle2ioCNnKPm7FEB/MAiT+ug==",
|
||||
"linux-armv7": "sha512-6OCChowE5lBXXXAZrnGdA9dVktg7UdODEBpE5qTroiAJYZv4yXRMgyDFYajok7du2NTgoklhxGk8d9+4vGv5hg==",
|
||||
"linux-x64": "sha512-OTmlmP2r8ozGKdB96X+K5oQE1ojVZanqLqqKlwDpEnfixyIaDGYbVzcjWBNGU3ai/26bvkaCkjynnc2ecYcsuA==",
|
||||
"linuxmusl-arm64v8": "sha512-Qh5Wi+bkKTohFYHzSPssfjMhIkD6z6EHbVmnwmWSsgY9zsUBStFp6+mKcNTQfP5YM5Mz06vJOkLHX2OzEr5TzA==",
|
||||
"linuxmusl-x64": "sha512-DwB4Fs3+ISw9etaLCANkueZDdk758iOS+wNp4TKZkHdq0al6B/3Pk7OHLR8a9E3H6wYDD328u++dcJzip5tacA==",
|
||||
"win32-arm64v8": "sha512-96r3W+O4BtX602B1MtxU5Ru4lKzRRTZqM4OQEBJ//TNL3fiCZdd9agD+RQBjaeR4KFIyBSt3F7IE425ZWmxz+w==",
|
||||
"win32-ia32": "sha512-qfN1MsfQGek1QQd1UNW7JT+5K5Ne1suFQ2GpgpYm3JLSpIve/tz2vOGEGzvTVssOBADJvAkTDFt+yIi3PgU9pA==",
|
||||
"win32-x64": "sha512-eb3aAmjbVVBVRbiYgebQwoxkAt69WI8nwmKlilSQ3kWqoc0pXfIe322rF2UR8ebbISCGvYRUfzD2r1k92RXISQ=="
|
||||
},
|
||||
"runtime": "napi",
|
||||
"target": 5
|
||||
"target": 7
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
"node": ">=14.15.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"binary": {
|
||||
"napi_versions": [
|
||||
5
|
||||
7
|
||||
]
|
||||
},
|
||||
"semistandard": {
|
||||
|
||||
173
src/common.cc
@@ -48,6 +48,9 @@ namespace sharp {
|
||||
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().Int32Value();
|
||||
}
|
||||
int64_t AttrAsInt64(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().Int64Value();
|
||||
}
|
||||
double AttrAsDouble(Napi::Object obj, std::string attr) {
|
||||
return obj.Get(attr).As<Napi::Number>().DoubleValue();
|
||||
}
|
||||
@@ -73,6 +76,14 @@ namespace sharp {
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len) {
|
||||
try {
|
||||
return Napi::Buffer<char>::New(env, data, len, FreeCallback);
|
||||
} catch (Napi::Error const &err) {}
|
||||
Napi::Buffer<char> buf = Napi::Buffer<char>::Copy(env, data, len);
|
||||
FreeCallback(nullptr, data);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
|
||||
@@ -85,16 +96,14 @@ namespace sharp {
|
||||
descriptor->buffer = buffer.Data();
|
||||
descriptor->isBuffer = TRUE;
|
||||
}
|
||||
descriptor->failOnError = AttrAsBool(input, "failOnError");
|
||||
descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
|
||||
// Density for vector-based input
|
||||
if (HasAttr(input, "density")) {
|
||||
descriptor->density = AttrAsDouble(input, "density");
|
||||
}
|
||||
// Raw pixel input
|
||||
if (HasAttr(input, "rawChannels")) {
|
||||
descriptor->rawDepth = static_cast<VipsBandFormat>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
||||
AttrAsStr(input, "rawDepth").data()));
|
||||
descriptor->rawDepth = AttrAsEnum<VipsBandFormat>(input, "rawDepth", VIPS_TYPE_BAND_FORMAT);
|
||||
descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
|
||||
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
|
||||
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
|
||||
@@ -128,11 +137,42 @@ namespace sharp {
|
||||
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
||||
}
|
||||
}
|
||||
// Create new image with text
|
||||
if (HasAttr(input, "textValue")) {
|
||||
descriptor->textValue = AttrAsStr(input, "textValue");
|
||||
if (HasAttr(input, "textFont")) {
|
||||
descriptor->textFont = AttrAsStr(input, "textFont");
|
||||
}
|
||||
if (HasAttr(input, "textFontfile")) {
|
||||
descriptor->textFontfile = AttrAsStr(input, "textFontfile");
|
||||
}
|
||||
if (HasAttr(input, "textWidth")) {
|
||||
descriptor->textWidth = AttrAsUint32(input, "textWidth");
|
||||
}
|
||||
if (HasAttr(input, "textHeight")) {
|
||||
descriptor->textHeight = AttrAsUint32(input, "textHeight");
|
||||
}
|
||||
if (HasAttr(input, "textAlign")) {
|
||||
descriptor->textAlign = AttrAsEnum<VipsAlign>(input, "textAlign", VIPS_TYPE_ALIGN);
|
||||
}
|
||||
if (HasAttr(input, "textJustify")) {
|
||||
descriptor->textJustify = AttrAsBool(input, "textJustify");
|
||||
}
|
||||
if (HasAttr(input, "textDpi")) {
|
||||
descriptor->textDpi = AttrAsUint32(input, "textDpi");
|
||||
}
|
||||
if (HasAttr(input, "textRgba")) {
|
||||
descriptor->textRgba = AttrAsBool(input, "textRgba");
|
||||
}
|
||||
if (HasAttr(input, "textSpacing")) {
|
||||
descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
|
||||
}
|
||||
}
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
||||
descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
|
||||
// Allow switch from random to sequential access
|
||||
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||
// Remove safety features and allow unlimited SVG/PNG input
|
||||
// Remove safety features and allow unlimited input
|
||||
descriptor->unlimited = AttrAsBool(input, "unlimited");
|
||||
return descriptor;
|
||||
}
|
||||
@@ -175,6 +215,9 @@ namespace sharp {
|
||||
bool IsAvif(std::string const &str) {
|
||||
return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
|
||||
}
|
||||
bool IsJxl(std::string const &str) {
|
||||
return EndsWith(str, ".jxl") || EndsWith(str, ".JXL");
|
||||
}
|
||||
bool IsDz(std::string const &str) {
|
||||
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
||||
}
|
||||
@@ -205,6 +248,7 @@ namespace sharp {
|
||||
case ImageType::PPM: id = "ppm"; break;
|
||||
case ImageType::FITS: id = "fits"; break;
|
||||
case ImageType::EXR: id = "exr"; break;
|
||||
case ImageType::JXL: id = "jxl"; break;
|
||||
case ImageType::VIPS: id = "vips"; break;
|
||||
case ImageType::RAW: id = "raw"; break;
|
||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||
@@ -245,10 +289,12 @@ namespace sharp {
|
||||
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
|
||||
{ "VipsForeignLoadMagick7File", ImageType::MAGICK },
|
||||
{ "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
|
||||
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
|
||||
{ "VipsForeignLoadOpenslideFile", ImageType::OPENSLIDE },
|
||||
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
||||
{ "VipsForeignLoadFits", ImageType::FITS },
|
||||
{ "VipsForeignLoadFitsFile", ImageType::FITS },
|
||||
{ "VipsForeignLoadOpenexr", ImageType::EXR },
|
||||
{ "VipsForeignLoadJxlFile", ImageType::JXL },
|
||||
{ "VipsForeignLoadJxlBuffer", ImageType::JXL },
|
||||
{ "VipsForeignLoadVips", ImageType::VIPS },
|
||||
{ "VipsForeignLoadVipsFile", ImageType::VIPS },
|
||||
{ "VipsForeignLoadRaw", ImageType::RAW }
|
||||
@@ -302,6 +348,17 @@ namespace sharp {
|
||||
imageType == ImageType::PDF;
|
||||
}
|
||||
|
||||
/*
|
||||
Does this image type support removal of safety limits?
|
||||
*/
|
||||
bool ImageTypeSupportsUnlimited(ImageType imageType) {
|
||||
return
|
||||
imageType == ImageType::JPEG ||
|
||||
imageType == ImageType::PNG ||
|
||||
imageType == ImageType::SVG ||
|
||||
imageType == ImageType::HEIF;
|
||||
}
|
||||
|
||||
/*
|
||||
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||
*/
|
||||
@@ -329,8 +386,8 @@ namespace sharp {
|
||||
try {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", descriptor->access)
|
||||
->set("fail", descriptor->failOnError);
|
||||
if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
|
||||
->set("fail_on", descriptor->failOn);
|
||||
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
||||
option->set("unlimited", TRUE);
|
||||
}
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||
@@ -361,51 +418,79 @@ namespace sharp {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (descriptor->createChannels > 0) {
|
||||
int const channels = descriptor->createChannels;
|
||||
if (channels > 0) {
|
||||
// Create new image
|
||||
if (descriptor->createNoiseType == "gaussian") {
|
||||
int const channels = descriptor->createChannels;
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
|
||||
std::vector<VImage> bands = {};
|
||||
bands.reserve(channels);
|
||||
for (int _band = 0; _band < channels; _band++) {
|
||||
bands.push_back(image.gaussnoise(
|
||||
descriptor->createWidth,
|
||||
descriptor->createHeight,
|
||||
VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
|
||||
}
|
||||
image = image.bandjoin(bands);
|
||||
image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
|
||||
if (channels < 3) {
|
||||
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
||||
} else {
|
||||
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||
bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
|
||||
->set("mean", descriptor->createNoiseMean)
|
||||
->set("sigma", descriptor->createNoiseSigma)));
|
||||
}
|
||||
image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
|
||||
channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
|
||||
} else {
|
||||
std::vector<double> background = {
|
||||
descriptor->createBackground[0],
|
||||
descriptor->createBackground[1],
|
||||
descriptor->createBackground[2]
|
||||
};
|
||||
if (descriptor->createChannels == 4) {
|
||||
if (channels == 4) {
|
||||
background.push_back(descriptor->createBackground[3]);
|
||||
}
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
|
||||
.copy(VImage::option()->set("interpretation",
|
||||
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
|
||||
.new_from_image(background);
|
||||
}
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
imageType = ImageType::RAW;
|
||||
} else if (descriptor->textValue.length() > 0) {
|
||||
// Create a new image with text
|
||||
vips::VOption *textOptions = VImage::option()
|
||||
->set("align", descriptor->textAlign)
|
||||
->set("justify", descriptor->textJustify)
|
||||
->set("rgba", descriptor->textRgba)
|
||||
->set("spacing", descriptor->textSpacing)
|
||||
->set("autofit_dpi", &descriptor->textAutofitDpi);
|
||||
if (descriptor->textWidth > 0) {
|
||||
textOptions->set("width", descriptor->textWidth);
|
||||
}
|
||||
// Ignore dpi if height is set
|
||||
if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
|
||||
textOptions->set("height", descriptor->textHeight);
|
||||
} else if (descriptor->textDpi > 0) {
|
||||
textOptions->set("dpi", descriptor->textDpi);
|
||||
}
|
||||
if (descriptor->textFont.length() > 0) {
|
||||
textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
|
||||
}
|
||||
if (descriptor->textFontfile.length() > 0) {
|
||||
textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
|
||||
}
|
||||
image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
|
||||
if (!descriptor->textRgba) {
|
||||
image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
|
||||
}
|
||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
// From filesystem
|
||||
imageType = DetermineImageType(descriptor->file.data());
|
||||
if (imageType == ImageType::MISSING) {
|
||||
throw vips::VError("Input file is 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: " + descriptor->file);
|
||||
}
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", descriptor->access)
|
||||
->set("fail", descriptor->failOnError);
|
||||
if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
|
||||
->set("fail_on", descriptor->failOn);
|
||||
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
||||
option->set("unlimited", TRUE);
|
||||
}
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||
@@ -436,9 +521,10 @@ namespace sharp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
if (descriptor->limitInputPixels > 0 &&
|
||||
static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(descriptor->limitInputPixels)) {
|
||||
static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
|
||||
throw vips::VError("Input image exceeds pixel limit");
|
||||
}
|
||||
return std::make_tuple(image, imageType);
|
||||
@@ -485,6 +571,7 @@ namespace sharp {
|
||||
VImage RemoveExifOrientation(VImage image) {
|
||||
VImage copy = image.copy();
|
||||
copy.remove(VIPS_META_ORIENTATION);
|
||||
copy.remove("exif-ifd0-Orientation");
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -633,7 +720,6 @@ namespace sharp {
|
||||
Event listener for progress updates, used to detect timeout
|
||||
*/
|
||||
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
|
||||
// printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
|
||||
if (*timeout > 0 && progress->run >= *timeout) {
|
||||
vips_image_set_kill(im, TRUE);
|
||||
vips_error("timeout", "%d%% complete", progress->percent);
|
||||
@@ -790,22 +876,6 @@ namespace sharp {
|
||||
return Is16Bit(interpretation) ? 65535.0 : 255.0;
|
||||
}
|
||||
|
||||
/*
|
||||
Get boolean operation type from string
|
||||
*/
|
||||
VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
|
||||
return static_cast<VipsOperationBoolean>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()));
|
||||
}
|
||||
|
||||
/*
|
||||
Get interpretation type from string
|
||||
*/
|
||||
VipsInterpretation GetInterpretation(std::string const typeStr) {
|
||||
return static_cast<VipsInterpretation>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()));
|
||||
}
|
||||
|
||||
/*
|
||||
Convert RGBA value to another colourspace
|
||||
*/
|
||||
@@ -857,7 +927,7 @@ namespace sharp {
|
||||
// Add non-transparent alpha channel, if required
|
||||
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
||||
image = image.bandjoin(
|
||||
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
|
||||
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
|
||||
}
|
||||
return std::make_tuple(image, alphaColour);
|
||||
}
|
||||
@@ -886,7 +956,7 @@ namespace sharp {
|
||||
|
||||
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
||||
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
|
||||
if (swap) {
|
||||
if (swap && canvas != Canvas::IGNORE_ASPECT) {
|
||||
// Swap input width and height when requested.
|
||||
std::swap(width, height);
|
||||
}
|
||||
@@ -917,9 +987,6 @@ namespace sharp {
|
||||
}
|
||||
break;
|
||||
case Canvas::IGNORE_ASPECT:
|
||||
if (swap) {
|
||||
std::swap(hshrink, vshrink);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (targetWidth > 0) {
|
||||
|
||||
55
src/common.h
@@ -25,9 +25,9 @@
|
||||
// Verify platform and compiler compatibility
|
||||
|
||||
#if (VIPS_MAJOR_VERSION < 8) || \
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 12) || \
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 12 && VIPS_MICRO_VERSION < 2)
|
||||
#error "libvips version 8.12.2+ is required - please see https://sharp.pixelplumbing.com/install"
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 13) || \
|
||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 3)
|
||||
#error "libvips version 8.13.3+ is required - please see https://sharp.pixelplumbing.com/install"
|
||||
#endif
|
||||
|
||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||
@@ -48,8 +48,8 @@ namespace sharp {
|
||||
std::string name;
|
||||
std::string file;
|
||||
char *buffer;
|
||||
bool failOnError;
|
||||
int limitInputPixels;
|
||||
VipsFailOn failOn;
|
||||
uint64_t limitInputPixels;
|
||||
bool unlimited;
|
||||
VipsAccess access;
|
||||
size_t bufferLength;
|
||||
@@ -71,10 +71,21 @@ namespace sharp {
|
||||
std::string createNoiseType;
|
||||
double createNoiseMean;
|
||||
double createNoiseSigma;
|
||||
std::string textValue;
|
||||
std::string textFont;
|
||||
std::string textFontfile;
|
||||
int textWidth;
|
||||
int textHeight;
|
||||
VipsAlign textAlign;
|
||||
bool textJustify;
|
||||
int textDpi;
|
||||
bool textRgba;
|
||||
int textSpacing;
|
||||
int textAutofitDpi;
|
||||
|
||||
InputDescriptor():
|
||||
buffer(nullptr),
|
||||
failOnError(TRUE),
|
||||
failOn(VIPS_FAIL_ON_WARNING),
|
||||
limitInputPixels(0x3FFF * 0x3FFF),
|
||||
unlimited(FALSE),
|
||||
access(VIPS_ACCESS_RANDOM),
|
||||
@@ -95,7 +106,15 @@ namespace sharp {
|
||||
createHeight(0),
|
||||
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
createNoiseMean(0.0),
|
||||
createNoiseSigma(0.0) {}
|
||||
createNoiseSigma(0.0),
|
||||
textWidth(0),
|
||||
textHeight(0),
|
||||
textAlign(VIPS_ALIGN_LOW),
|
||||
textJustify(FALSE),
|
||||
textDpi(72),
|
||||
textRgba(FALSE),
|
||||
textSpacing(0),
|
||||
textAutofitDpi(0) {}
|
||||
};
|
||||
|
||||
// Convenience methods to access the attributes of a Napi::Object
|
||||
@@ -110,6 +129,11 @@ namespace sharp {
|
||||
bool AttrAsBool(Napi::Object obj, std::string attr);
|
||||
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
|
||||
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
|
||||
template <class T> T AttrAsEnum(Napi::Object obj, std::string attr, GType type) {
|
||||
return static_cast<T>(
|
||||
vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
|
||||
}
|
||||
Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len);
|
||||
|
||||
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||
InputDescriptor* CreateInputDescriptor(Napi::Object input);
|
||||
@@ -129,6 +153,7 @@ namespace sharp {
|
||||
PPM,
|
||||
FITS,
|
||||
EXR,
|
||||
JXL,
|
||||
VIPS,
|
||||
RAW,
|
||||
UNKNOWN,
|
||||
@@ -159,6 +184,7 @@ namespace sharp {
|
||||
bool IsHeic(std::string const &str);
|
||||
bool IsHeif(std::string const &str);
|
||||
bool IsAvif(std::string const &str);
|
||||
bool IsJxl(std::string const &str);
|
||||
bool IsDz(std::string const &str);
|
||||
bool IsDzZip(std::string const &str);
|
||||
bool IsV(std::string const &str);
|
||||
@@ -183,6 +209,11 @@ namespace sharp {
|
||||
*/
|
||||
bool ImageTypeSupportsPage(ImageType imageType);
|
||||
|
||||
/*
|
||||
Does this image type support removal of safety limits?
|
||||
*/
|
||||
bool ImageTypeSupportsUnlimited(ImageType imageType);
|
||||
|
||||
/*
|
||||
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||
*/
|
||||
@@ -307,16 +338,6 @@ namespace sharp {
|
||||
*/
|
||||
double MaximumImageAlpha(VipsInterpretation const interpretation);
|
||||
|
||||
/*
|
||||
Get boolean operation type from string
|
||||
*/
|
||||
VipsOperationBoolean GetBooleanOperation(std::string const opStr);
|
||||
|
||||
/*
|
||||
Get interpretation type from string
|
||||
*/
|
||||
VipsInterpretation GetInterpretation(std::string const typeStr);
|
||||
|
||||
/*
|
||||
Convert RGBA value to another colourspace
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
@@ -733,7 +732,7 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
|
||||
g_object_get( target.get_target(), "blob", &blob, NULL );
|
||||
g_object_get( target.get_target(), "blob", &blob, (void *) NULL );
|
||||
}
|
||||
else if( (operation_name = vips_foreign_find_save_buffer( filename )) ) {
|
||||
call_option_string( operation_name, option_string,
|
||||
@@ -778,6 +777,32 @@ VImage::write_to_target( const char *suffix, VTarget target,
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region() const
|
||||
{
|
||||
return VRegion::new_from_image( *this );
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region( VipsRect *rect ) const
|
||||
{
|
||||
VRegion region = VRegion::new_from_image( *this );
|
||||
|
||||
region.prepare( rect );
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
VRegion
|
||||
VImage::region( int left, int top, int width, int height ) const
|
||||
{
|
||||
VRegion region = VRegion::new_from_image( *this );
|
||||
|
||||
region.prepare( left, top, width, height );
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
#include "vips-operators.cpp"
|
||||
|
||||
std::vector<VImage>
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
|
||||
27
src/libvips/cplusplus/VRegion.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// Object part of VRegion class
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
#include <vips/debug.h>
|
||||
|
||||
VIPS_NAMESPACE_START
|
||||
|
||||
VRegion
|
||||
VRegion::new_from_image( VImage image )
|
||||
{
|
||||
VipsRegion *region;
|
||||
|
||||
if( !(region = vips_region_new( image.get_image() )) ) {
|
||||
throw VError();
|
||||
}
|
||||
|
||||
VRegion out( region );
|
||||
|
||||
return( out );
|
||||
}
|
||||
|
||||
VIPS_NAMESPACE_END
|
||||
@@ -1,5 +1,4 @@
|
||||
// bodies for vips operations
|
||||
// Mon Nov 1 03:31:09 PM CET 2021
|
||||
// this file is generated automatically, do not edit!
|
||||
|
||||
VImage VImage::CMC2LCh( VOption *options ) const
|
||||
@@ -943,6 +942,14 @@ VipsBlob *VImage::dzsave_buffer( VOption *options ) const
|
||||
return( buffer );
|
||||
}
|
||||
|
||||
void VImage::dzsave_target( VTarget target, VOption *options ) const
|
||||
{
|
||||
call( "dzsave_target",
|
||||
(options ? options : VImage::option())->
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VImage VImage::embed( int x, int y, int width, int height, VOption *options ) const
|
||||
{
|
||||
VImage out;
|
||||
@@ -3521,6 +3528,14 @@ VipsBlob *VImage::tiffsave_buffer( VOption *options ) const
|
||||
return( buffer );
|
||||
}
|
||||
|
||||
void VImage::tiffsave_target( VTarget target, VOption *options ) const
|
||||
{
|
||||
call( "tiffsave_target",
|
||||
(options ? options : VImage::option())->
|
||||
set( "in", *this )->
|
||||
set( "target", target ) );
|
||||
}
|
||||
|
||||
VImage VImage::tilecache( VOption *options ) const
|
||||
{
|
||||
VImage out;
|
||||
|
||||
@@ -235,20 +235,20 @@ class MetadataWorker : public Napi::AsyncWorker {
|
||||
info.Set("orientation", baton->orientation);
|
||||
}
|
||||
if (baton->exifLength > 0) {
|
||||
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
|
||||
info.Set("exif", sharp::NewOrCopyBuffer(env, baton->exif, baton->exifLength));
|
||||
}
|
||||
if (baton->iccLength > 0) {
|
||||
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
|
||||
info.Set("icc", sharp::NewOrCopyBuffer(env, baton->icc, baton->iccLength));
|
||||
}
|
||||
if (baton->iptcLength > 0) {
|
||||
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
|
||||
info.Set("iptc", sharp::NewOrCopyBuffer(env, baton->iptc, baton->iptcLength));
|
||||
}
|
||||
if (baton->xmpLength > 0) {
|
||||
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
|
||||
info.Set("xmp", sharp::NewOrCopyBuffer(env, baton->xmp, baton->xmpLength));
|
||||
}
|
||||
if (baton->tifftagPhotoshopLength > 0) {
|
||||
info.Set("tifftagPhotoshop",
|
||||
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
||||
sharp::NewOrCopyBuffer(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength));
|
||||
}
|
||||
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
||||
} else {
|
||||
|
||||
@@ -68,10 +68,9 @@ namespace sharp {
|
||||
// Extract luminance
|
||||
VImage luminance = lab[0];
|
||||
// Find luminance range
|
||||
VImage stats = luminance.stats();
|
||||
double min = stats(0, 0)[0];
|
||||
double max = stats(1, 0)[0];
|
||||
if (min != max) {
|
||||
int const min = luminance.percent(1);
|
||||
int const max = luminance.percent(99);
|
||||
if (std::abs(max - min) > 1) {
|
||||
// Extract chroma
|
||||
VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
|
||||
// Calculate multiplication factor and addition
|
||||
@@ -112,6 +111,19 @@ namespace sharp {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Flatten image to remove alpha channel
|
||||
*/
|
||||
VImage Flatten(VImage image, std::vector<double> flattenBackground) {
|
||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||
std::vector<double> background {
|
||||
flattenBackground[0] * multiplier,
|
||||
flattenBackground[1] * multiplier,
|
||||
flattenBackground[2] * multiplier
|
||||
};
|
||||
return image.flatten(VImage::option()->set("background", background));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the "negative" of the image.
|
||||
*/
|
||||
@@ -209,7 +221,8 @@ namespace sharp {
|
||||
/*
|
||||
* 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) {
|
||||
// Fast, mild sharpen
|
||||
VImage sharpen = VImage::new_matrixv(3, 3,
|
||||
@@ -224,8 +237,14 @@ namespace sharp {
|
||||
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
|
||||
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
|
||||
}
|
||||
return image.sharpen(
|
||||
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
|
||||
return image
|
||||
.sharpen(VImage::option()
|
||||
->set("sigma", sigma)
|
||||
->set("m1", m1)
|
||||
->set("m2", m2)
|
||||
->set("x1", x1)
|
||||
->set("y2", y2)
|
||||
->set("y3", y3))
|
||||
.colourspace(colourspaceBeforeSharpen);
|
||||
}
|
||||
}
|
||||
@@ -255,45 +274,80 @@ namespace sharp {
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, double const threshold) {
|
||||
VImage Trim(VImage image, std::vector<double> background, double threshold) {
|
||||
if (image.width() < 3 && image.height() < 3) {
|
||||
throw VError("Image to trim must be at least 3x3 pixels");
|
||||
}
|
||||
// Top-left pixel provides the background colour
|
||||
VImage background = image.extract_area(0, 0, 1, 1);
|
||||
if (HasAlpha(background)) {
|
||||
background = background.flatten();
|
||||
|
||||
// Scale up 8-bit values to match 16-bit input image
|
||||
double multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||
threshold *= multiplier;
|
||||
|
||||
std::vector<double> backgroundAlpha(1);
|
||||
if (background.size() == 0) {
|
||||
// Top-left pixel provides the default background colour if none is given
|
||||
background = image.extract_area(0, 0, 1, 1)(0, 0);
|
||||
multiplier = 1.0;
|
||||
}
|
||||
if (HasAlpha(image) && background.size() == 4) {
|
||||
// Just discard the alpha because flattening the background colour with
|
||||
// itself (effectively what find_trim() does) gives the same result
|
||||
backgroundAlpha[0] = background[3] * multiplier;
|
||||
}
|
||||
if (image.bands() > 2) {
|
||||
background = {
|
||||
background[0] * multiplier,
|
||||
background[1] * multiplier,
|
||||
background[2] * multiplier
|
||||
};
|
||||
} else {
|
||||
background[0] = background[0] * multiplier;
|
||||
}
|
||||
int left, top, width, height;
|
||||
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", background(0, 0))
|
||||
->set("background", background)
|
||||
->set("threshold", threshold));
|
||||
if (width == 0 || height == 0) {
|
||||
if (HasAlpha(image)) {
|
||||
// Search alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
|
||||
left = alpha.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", backgroundAlpha(0, 0))
|
||||
->set("threshold", threshold));
|
||||
}
|
||||
if (width == 0 || height == 0) {
|
||||
throw VError("Unexpected error while trimming. Try to lower the tolerance");
|
||||
if (HasAlpha(image)) {
|
||||
// Search alpha channel (A)
|
||||
int leftA, topA, widthA, heightA;
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
|
||||
->set("background", backgroundAlpha)
|
||||
->set("threshold", threshold));
|
||||
if (widthA > 0 && heightA > 0) {
|
||||
if (width > 0 && height > 0) {
|
||||
// Combined bounding box (B)
|
||||
int const leftB = std::min(left, leftA);
|
||||
int const topB = std::min(top, topA);
|
||||
int const widthB = std::max(left + width, leftA + widthA) - leftB;
|
||||
int const heightB = std::max(top + height, topA + heightA) - topB;
|
||||
return image.extract_area(leftB, topB, widthB, heightB);
|
||||
} else {
|
||||
// Use alpha only
|
||||
return image.extract_area(leftA, topA, widthA, heightA);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image.extract_area(left, top, width, height);
|
||||
if (width > 0 && height > 0) {
|
||||
return image.extract_area(left, top, width, height);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate (a * in + b)
|
||||
*/
|
||||
VImage Linear(VImage image, double const a, double const b) {
|
||||
if (HasAlpha(image)) {
|
||||
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
|
||||
size_t const bands = static_cast<size_t>(image.bands());
|
||||
if (a.size() > bands) {
|
||||
throw VError("Band expansion using linear is unsupported");
|
||||
}
|
||||
if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
|
||||
// Separate alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
|
||||
VImage alpha = image[bands - 1];
|
||||
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", TRUE)).bandjoin(alpha);
|
||||
} else {
|
||||
return image.linear(a, b);
|
||||
return image.linear(a, b, VImage::option()->set("uchar", TRUE));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ namespace sharp {
|
||||
*/
|
||||
VImage Gamma(VImage image, double const exponent);
|
||||
|
||||
/*
|
||||
* Flatten image to remove alpha channel
|
||||
*/
|
||||
VImage Flatten(VImage image, std::vector<double> flattenBackground);
|
||||
|
||||
/*
|
||||
* Produce the "negative" of the image.
|
||||
*/
|
||||
@@ -64,7 +69,8 @@ namespace sharp {
|
||||
/*
|
||||
* 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
|
||||
@@ -84,12 +90,12 @@ namespace sharp {
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, double const threshold);
|
||||
VImage Trim(VImage image, std::vector<double> background, double const threshold);
|
||||
|
||||
/*
|
||||
* Linear adjustment (a * in + b)
|
||||
*/
|
||||
VImage Linear(VImage image, double const a, double const b);
|
||||
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
|
||||
|
||||
/*
|
||||
* Recomb with a Matrix of the given bands/channel size.
|
||||
|
||||
498
src/pipeline.cc
@@ -81,29 +81,46 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
int pageHeight = sharp::GetPageHeight(image);
|
||||
|
||||
// Calculate angle of rotation
|
||||
VipsAngle rotation;
|
||||
bool flip = FALSE;
|
||||
bool flop = FALSE;
|
||||
VipsAngle rotation = VIPS_ANGLE_D0;
|
||||
VipsAngle autoRotation = VIPS_ANGLE_D0;
|
||||
bool autoFlip = FALSE;
|
||||
bool autoFlop = FALSE;
|
||||
if (baton->useExifOrientation) {
|
||||
// Rotate and flip image according to Exif orientation
|
||||
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
||||
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
||||
image = sharp::RemoveExifOrientation(image);
|
||||
} else {
|
||||
rotation = CalculateAngleRotation(baton->angle);
|
||||
}
|
||||
|
||||
// Rotate pre-extract
|
||||
if (baton->rotateBeforePreExtract) {
|
||||
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
|
||||
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
|
||||
autoFlip || baton->flip || autoFlop || baton->flop ||
|
||||
baton->rotationAngle != 0.0);
|
||||
|
||||
if (shouldRotateBefore) {
|
||||
if (autoRotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(autoRotation);
|
||||
autoRotation = VIPS_ANGLE_D0;
|
||||
}
|
||||
if (autoFlip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
autoFlip = FALSE;
|
||||
} else if (baton->flip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
baton->flip = FALSE;
|
||||
}
|
||||
if (autoFlop) {
|
||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||
autoFlop = FALSE;
|
||||
} else if (baton->flop) {
|
||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||
baton->flop = FALSE;
|
||||
}
|
||||
if (rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
if (flip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
flip = FALSE;
|
||||
}
|
||||
if (flop) {
|
||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||
flop = FALSE;
|
||||
}
|
||||
image = sharp::RemoveExifOrientation(image);
|
||||
rotation = VIPS_ANGLE_D0;
|
||||
}
|
||||
if (baton->rotationAngle != 0.0) {
|
||||
MultiPageUnsupported(nPages, "Rotate");
|
||||
@@ -116,7 +133,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Trim
|
||||
if (baton->trimThreshold > 0.0) {
|
||||
MultiPageUnsupported(nPages, "Trim");
|
||||
image = sharp::Trim(image, baton->trimThreshold);
|
||||
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
|
||||
baton->trimOffsetLeft = image.xoffset();
|
||||
baton->trimOffsetTop = image.yoffset();
|
||||
}
|
||||
@@ -145,7 +162,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
int targetResizeHeight = baton->height;
|
||||
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
|
||||
bool swap = !baton->rotateBeforePreExtract &&
|
||||
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270 ||
|
||||
autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270);
|
||||
|
||||
// Shrink to pageHeight, so we work for multi-page images
|
||||
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
||||
@@ -165,7 +184,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// - input colourspace is not specified;
|
||||
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
||||
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
|
||||
baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
|
||||
baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
|
||||
|
||||
if (shouldPreShrink) {
|
||||
// The common part of the shrink: the bit by which both axes must be shrunk
|
||||
@@ -186,8 +205,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
|
||||
jpegShrinkOnLoad /= 2;
|
||||
}
|
||||
} else if (inputImageType == sharp::ImageType::WEBP ||
|
||||
inputImageType == sharp::ImageType::SVG ||
|
||||
} else if (inputImageType == sharp::ImageType::WEBP && shrink > 1.0) {
|
||||
// Avoid upscaling via webp
|
||||
scale = 1.0 / shrink;
|
||||
} else if (inputImageType == sharp::ImageType::SVG ||
|
||||
inputImageType == sharp::ImageType::PDF) {
|
||||
scale = 1.0 / shrink;
|
||||
}
|
||||
@@ -200,7 +221,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", baton->input->access)
|
||||
->set("shrink", jpegShrinkOnLoad)
|
||||
->set("fail", baton->input->failOnError);
|
||||
->set("unlimited", baton->input->unlimited)
|
||||
->set("fail_on", baton->input->failOn);
|
||||
if (baton->input->buffer != nullptr) {
|
||||
// Reload JPEG buffer
|
||||
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
||||
@@ -214,7 +236,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
vips::VOption *option = VImage::option()
|
||||
->set("access", baton->input->access)
|
||||
->set("scale", scale)
|
||||
->set("fail", baton->input->failOnError);
|
||||
->set("fail_on", baton->input->failOn);
|
||||
if (inputImageType == sharp::ImageType::WEBP) {
|
||||
option->set("n", baton->input->pages);
|
||||
option->set("page", baton->input->page);
|
||||
@@ -241,8 +263,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Reload SVG file
|
||||
image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
|
||||
}
|
||||
|
||||
sharp::SetDensity(image, baton->input->density);
|
||||
if (image.width() > 32767 || image.height() > 32767) {
|
||||
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
|
||||
}
|
||||
} else if (inputImageType == sharp::ImageType::PDF) {
|
||||
option->set("n", baton->input->pages);
|
||||
option->set("page", baton->input->page);
|
||||
@@ -260,6 +284,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
|
||||
sharp::SetDensity(image, baton->input->density);
|
||||
}
|
||||
} else {
|
||||
if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
|
||||
throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
|
||||
}
|
||||
}
|
||||
|
||||
// Any pre-shrinking may already have been done
|
||||
@@ -311,16 +339,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
|
||||
// Flatten image to remove alpha channel
|
||||
if (baton->flatten && sharp::HasAlpha(image)) {
|
||||
// Scale up 8-bit values to match 16-bit input image
|
||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||
// Background colour
|
||||
std::vector<double> background {
|
||||
baton->flattenBackground[0] * multiplier,
|
||||
baton->flattenBackground[1] * multiplier,
|
||||
baton->flattenBackground[2] * multiplier
|
||||
};
|
||||
image = image.flatten(VImage::option()
|
||||
->set("background", background));
|
||||
image = sharp::Flatten(image, baton->flattenBackground);
|
||||
}
|
||||
|
||||
// Negate the colours in the image
|
||||
@@ -342,65 +361,41 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
bool const shouldBlur = baton->blurSigma != 0.0;
|
||||
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
||||
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
||||
bool const shouldApplyMedian = baton->medianSize > 0;
|
||||
bool const shouldComposite = !baton->composite.empty();
|
||||
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
||||
baton->hue != 0.0 || baton->lightness != 0.0;
|
||||
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
||||
|
||||
if (shouldComposite && !sharp::HasAlpha(image)) {
|
||||
image = sharp::EnsureAlpha(image, 1);
|
||||
}
|
||||
|
||||
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
|
||||
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
|
||||
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
|
||||
|
||||
// Premultiply image alpha channel before all transformations to avoid
|
||||
// dark fringing around bright pixels
|
||||
// See: http://entropymine.com/imageworsener/resizealpha/
|
||||
if (shouldPremultiplyAlpha) {
|
||||
image = image.premultiply();
|
||||
}
|
||||
|
||||
// Resize
|
||||
if (shouldResize) {
|
||||
VipsKernel kernel = static_cast<VipsKernel>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
|
||||
if (
|
||||
kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
|
||||
kernel != VIPS_KERNEL_LANCZOS3 && kernel != VIPS_KERNEL_MITCHELL
|
||||
) {
|
||||
throw vips::VError("Unknown kernel");
|
||||
}
|
||||
image = image.resize(1.0 / hshrink, VImage::option()
|
||||
->set("vscale", 1.0 / vshrink)
|
||||
->set("kernel", kernel));
|
||||
->set("kernel", baton->kernel));
|
||||
}
|
||||
|
||||
// Rotate post-extract 90-angle
|
||||
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
if (flip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
flip = FALSE;
|
||||
}
|
||||
if (flop) {
|
||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||
flop = FALSE;
|
||||
}
|
||||
image = sharp::RemoveExifOrientation(image);
|
||||
// Auto-rotate post-extract
|
||||
if (autoRotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(autoRotation);
|
||||
}
|
||||
|
||||
// Flip (mirror about Y axis)
|
||||
if (baton->flip || flip) {
|
||||
if (baton->flip || autoFlip) {
|
||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||
image = sharp::RemoveExifOrientation(image);
|
||||
}
|
||||
|
||||
// Flop (mirror about X axis)
|
||||
if (baton->flop || flop) {
|
||||
if (baton->flop || autoFlop) {
|
||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||
image = sharp::RemoveExifOrientation(image);
|
||||
}
|
||||
// Rotate post-extract 90-angle
|
||||
if (rotation != VIPS_ANGLE_D0) {
|
||||
image = image.rot(rotation);
|
||||
}
|
||||
|
||||
// Join additional color channels to the image
|
||||
@@ -434,18 +429,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
||||
|
||||
// Embed
|
||||
|
||||
// Calculate where to position the embedded image if gravity specified, else center.
|
||||
int left;
|
||||
int top;
|
||||
|
||||
left = static_cast<int>(round((baton->width - inputWidth) / 2));
|
||||
top = static_cast<int>(round((baton->height - inputHeight) / 2));
|
||||
|
||||
int width = std::max(inputWidth, baton->width);
|
||||
int height = std::max(inputHeight, baton->height);
|
||||
std::tie(left, top) = sharp::CalculateEmbedPosition(
|
||||
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
||||
int width = std::max(inputWidth, baton->width);
|
||||
int height = std::max(inputHeight, baton->height);
|
||||
|
||||
image = nPages > 1
|
||||
? sharp::EmbedMultiPage(image,
|
||||
@@ -544,7 +533,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
|
||||
}
|
||||
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
||||
if (shouldApplyMedian) {
|
||||
if (baton->medianSize > 0) {
|
||||
image = image.median(baton->medianSize);
|
||||
}
|
||||
// Threshold - must happen before blurring, due to the utility of blurring after thresholding
|
||||
@@ -570,17 +559,33 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = sharp::Recomb(image, baton->recombMatrix);
|
||||
}
|
||||
|
||||
if (shouldModulate) {
|
||||
// Modulate
|
||||
if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
|
||||
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
||||
}
|
||||
|
||||
// Sharpen
|
||||
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);
|
||||
}
|
||||
|
||||
// Reverse premultiplication after all transformations
|
||||
if (shouldPremultiplyAlpha) {
|
||||
image = image.unpremultiply();
|
||||
// Cast pixel values to integer
|
||||
if (sharp::Is16Bit(image.interpretation())) {
|
||||
image = image.cast(VIPS_FORMAT_USHORT);
|
||||
} else {
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
}
|
||||
}
|
||||
baton->premultiplied = shouldPremultiplyAlpha;
|
||||
|
||||
// Composite
|
||||
if (shouldComposite) {
|
||||
std::vector<VImage> images = { image };
|
||||
std::vector<int> modes, xs, ys;
|
||||
for (Composite *composite : baton->composite) {
|
||||
VImage compositeImage;
|
||||
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
||||
@@ -595,14 +600,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
int across = 0;
|
||||
int down = 0;
|
||||
// Use gravity in overlay
|
||||
if (compositeImage.width() <= baton->width) {
|
||||
if (compositeImage.width() <= image.width()) {
|
||||
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
|
||||
// Ensure odd number of tiles across when gravity is centre, north or south
|
||||
if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
|
||||
across |= 1;
|
||||
}
|
||||
}
|
||||
if (compositeImage.height() <= baton->height) {
|
||||
if (compositeImage.height() <= image.height()) {
|
||||
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
|
||||
// Ensure odd number of tiles down when gravity is centre, east or west
|
||||
if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
|
||||
@@ -626,12 +631,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// gravity was used for extract_area, set it back to its default value of 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);
|
||||
if (!sharp::HasAlpha(compositeImage)) {
|
||||
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
||||
}
|
||||
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
|
||||
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
||||
// Calculate position
|
||||
int left;
|
||||
int top;
|
||||
@@ -649,33 +654,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||
compositeImage.width(), compositeImage.height(), composite->gravity);
|
||||
}
|
||||
// Composite
|
||||
image = image.composite2(compositeImage, composite->mode, VImage::option()
|
||||
->set("premultiplied", TRUE)
|
||||
->set("x", left)
|
||||
->set("y", top));
|
||||
images.push_back(compositeImage);
|
||||
modes.push_back(composite->mode);
|
||||
xs.push_back(left);
|
||||
ys.push_back(top);
|
||||
}
|
||||
image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
|
||||
}
|
||||
|
||||
// Reverse premultiplication after all transformations:
|
||||
if (shouldPremultiplyAlpha) {
|
||||
image = image.unpremultiply();
|
||||
// Cast pixel values to integer
|
||||
if (sharp::Is16Bit(image.interpretation())) {
|
||||
image = image.cast(VIPS_FORMAT_USHORT);
|
||||
} else {
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
}
|
||||
}
|
||||
baton->premultiplied = shouldPremultiplyAlpha;
|
||||
|
||||
// Gamma decoding (brighten)
|
||||
if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
|
||||
image = sharp::Gamma(image, baton->gammaOut);
|
||||
}
|
||||
|
||||
// Linear adjustment (a * in + b)
|
||||
if (baton->linearA != 1.0 || baton->linearB != 0.0) {
|
||||
if (!baton->linearA.empty()) {
|
||||
image = sharp::Linear(image, baton->linearA, baton->linearB);
|
||||
}
|
||||
|
||||
@@ -685,7 +678,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
||||
if (shouldApplyClahe) {
|
||||
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
||||
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
||||
}
|
||||
|
||||
@@ -708,24 +701,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
||||
}
|
||||
|
||||
// Extract an image channel (aka vips band)
|
||||
if (baton->extractChannel > -1) {
|
||||
if (baton->extractChannel >= image.bands()) {
|
||||
if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
|
||||
baton->extractChannel = image.bands() - 1;
|
||||
} else {
|
||||
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
|
||||
return Error();
|
||||
}
|
||||
}
|
||||
VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
|
||||
? VIPS_INTERPRETATION_GREY16
|
||||
: VIPS_INTERPRETATION_B_W;
|
||||
image = image
|
||||
.extract_band(baton->extractChannel)
|
||||
.copy(VImage::option()->set("interpretation", interpretation));
|
||||
}
|
||||
|
||||
// Remove alpha channel, if any
|
||||
if (baton->removeAlpha) {
|
||||
image = sharp::RemoveAlpha(image);
|
||||
@@ -751,6 +726,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract channel
|
||||
if (baton->extractChannel > -1) {
|
||||
if (baton->extractChannel >= image.bands()) {
|
||||
if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
|
||||
baton->extractChannel = image.bands() - 1;
|
||||
} else {
|
||||
(baton->err)
|
||||
.append("Cannot extract channel ").append(std::to_string(baton->extractChannel))
|
||||
.append(" from image with channels 0-").append(std::to_string(image.bands() - 1));
|
||||
return Error();
|
||||
}
|
||||
}
|
||||
VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation())
|
||||
? VIPS_INTERPRETATION_GREY16
|
||||
: VIPS_INTERPRETATION_B_W;
|
||||
image = image
|
||||
.extract_band(baton->extractChannel)
|
||||
.copy(VImage::option()->set("interpretation", colourspace));
|
||||
}
|
||||
|
||||
// Apply output ICC profile
|
||||
if (!baton->withMetadataIcc.empty()) {
|
||||
image = image.icc_transform(
|
||||
@@ -859,6 +854,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
->set("smart_subsample", baton->webpSmartSubsample)
|
||||
->set("effort", baton->webpEffort)
|
||||
->set("min_size", baton->webpMinSize)
|
||||
->set("mixed", baton->webpMixed)
|
||||
->set("alpha_q", baton->webpAlphaQuality)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
@@ -873,6 +870,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reoptimise", baton->gifReoptimise)
|
||||
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
||||
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
||||
->set("dither", baton->gifDither)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
@@ -911,12 +911,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
} else if (baton->formatOut == "heif" ||
|
||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
||||
// Write HEIF to buffer
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
->set("bitdepth", 8)
|
||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||
->set("lossless", baton->heifLossless)));
|
||||
@@ -925,6 +926,34 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
area->free_fn = nullptr;
|
||||
vips_area_unref(area);
|
||||
baton->formatOut = "heif";
|
||||
} else if (baton->formatOut == "dz") {
|
||||
// Write DZ to buffer
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
if (!sharp::HasAlpha(image)) {
|
||||
baton->tileBackground.pop_back();
|
||||
}
|
||||
vips::VOption *options = BuildOptionsDZ(baton);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
area->free_fn = nullptr;
|
||||
vips_area_unref(area);
|
||||
baton->formatOut = "dz";
|
||||
} else if (baton->formatOut == "jxl" ||
|
||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
|
||||
// Write JXL to buffer
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
->set("lossless", baton->jxlLossless)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
area->free_fn = nullptr;
|
||||
vips_area_unref(area);
|
||||
baton->formatOut = "jxl";
|
||||
} else if (baton->formatOut == "raw" ||
|
||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
|
||||
// Write raw, uncompressed image data to buffer
|
||||
@@ -963,6 +992,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
||||
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
||||
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
||||
bool const isJxl = sharp::IsJxl(baton->fileOut);
|
||||
bool const isDz = sharp::IsDz(baton->fileOut);
|
||||
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
||||
bool const isV = sharp::IsV(baton->fileOut);
|
||||
@@ -1026,6 +1056,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
->set("smart_subsample", baton->webpSmartSubsample)
|
||||
->set("effort", baton->webpEffort)
|
||||
->set("min_size", baton->webpMinSize)
|
||||
->set("mixed", baton->webpMixed)
|
||||
->set("alpha_q", baton->webpAlphaQuality));
|
||||
baton->formatOut = "webp";
|
||||
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
||||
@@ -1036,6 +1068,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reoptimise", baton->gifReoptimise)
|
||||
->set("dither", baton->gifDither));
|
||||
baton->formatOut = "gif";
|
||||
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
||||
@@ -1066,75 +1099,37 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
||||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
||||
// Write HEIF to file
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
->set("bitdepth", 8)
|
||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||
->set("lossless", baton->heifLossless));
|
||||
baton->formatOut = "heif";
|
||||
} else if (baton->formatOut == "jxl" || (mightMatchInput && isJxl) ||
|
||||
(willMatchInput && inputImageType == sharp::ImageType::JXL)) {
|
||||
// Write JXL to file
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
->set("lossless", baton->jxlLossless));
|
||||
baton->formatOut = "jxl";
|
||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||
// Write DZ to file
|
||||
if (isDzZip) {
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
}
|
||||
// Forward format options through suffix
|
||||
std::string suffix;
|
||||
if (baton->tileFormat == "png") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
||||
{"compression", std::to_string(baton->pngCompressionLevel)},
|
||||
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
||||
};
|
||||
suffix = AssembleSuffixString(".png", options);
|
||||
} else if (baton->tileFormat == "webp") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->webpQuality)},
|
||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
||||
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
||||
{"effort", std::to_string(baton->webpEffort)}
|
||||
};
|
||||
suffix = AssembleSuffixString(".webp", options);
|
||||
} else {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->jpegQuality)},
|
||||
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
||||
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
||||
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
||||
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
||||
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
||||
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
||||
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
|
||||
};
|
||||
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
||||
suffix = AssembleSuffixString(extname, options);
|
||||
}
|
||||
// Remove alpha channel from tile background if image does not contain an alpha channel
|
||||
if (!sharp::HasAlpha(image)) {
|
||||
baton->tileBackground.pop_back();
|
||||
}
|
||||
// Write DZ to file
|
||||
vips::VOption *options = VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("tile_size", baton->tileSize)
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("container", baton->tileContainer)
|
||||
->set("layout", baton->tileLayout)
|
||||
->set("suffix", const_cast<char*>(suffix.data()))
|
||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||
->set("background", baton->tileBackground)
|
||||
->set("centre", baton->tileCentre)
|
||||
->set("id", const_cast<char*>(baton->tileId.data()))
|
||||
->set("skip_blanks", baton->tileSkipBlanks);
|
||||
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
||||
// not passing anything - libvips will handle choice
|
||||
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
|
||||
options->set("depth", baton->tileDepth);
|
||||
}
|
||||
vips::VOption *options = BuildOptionsDZ(baton);
|
||||
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
||||
baton->formatOut = "dz";
|
||||
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
|
||||
@@ -1203,12 +1198,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
|
||||
}
|
||||
|
||||
if (baton->input->textAutofitDpi) {
|
||||
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
|
||||
}
|
||||
|
||||
if (baton->bufferOutLength > 0) {
|
||||
// Add buffer size to info
|
||||
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
||||
// Pass ownership of output data to Buffer instance
|
||||
Napi::Buffer<char> data = Napi::Buffer<char>::New(env, static_cast<char*>(baton->bufferOut),
|
||||
baton->bufferOutLength, sharp::FreeCallback);
|
||||
Napi::Buffer<char> data = sharp::NewOrCopyBuffer(env, static_cast<char*>(baton->bufferOut),
|
||||
baton->bufferOutLength);
|
||||
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
|
||||
} else {
|
||||
// Add file size to info
|
||||
@@ -1291,7 +1290,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
|
||||
/*
|
||||
Assemble the suffix argument to dzsave, which is the format (by extname)
|
||||
alongisde comma-separated arguments to the corresponding `formatsave` vips
|
||||
alongside comma-separated arguments to the corresponding `formatsave` vips
|
||||
action.
|
||||
*/
|
||||
std::string
|
||||
@@ -1306,6 +1305,67 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
return extname + "[" + argument + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
Build VOption for dzsave
|
||||
*/
|
||||
vips::VOption*
|
||||
BuildOptionsDZ(PipelineBaton *baton) {
|
||||
// Forward format options through suffix
|
||||
std::string suffix;
|
||||
if (baton->tileFormat == "png") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
||||
{"compression", std::to_string(baton->pngCompressionLevel)},
|
||||
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
||||
};
|
||||
suffix = AssembleSuffixString(".png", options);
|
||||
} else if (baton->tileFormat == "webp") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->webpQuality)},
|
||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
||||
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
||||
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
|
||||
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
|
||||
{"effort", std::to_string(baton->webpEffort)}
|
||||
};
|
||||
suffix = AssembleSuffixString(".webp", options);
|
||||
} else {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->jpegQuality)},
|
||||
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
||||
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
||||
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
||||
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
||||
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
||||
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
||||
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
|
||||
};
|
||||
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
||||
suffix = AssembleSuffixString(extname, options);
|
||||
}
|
||||
vips::VOption *options = VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("tile_size", baton->tileSize)
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("container", baton->tileContainer)
|
||||
->set("layout", baton->tileLayout)
|
||||
->set("suffix", const_cast<char*>(suffix.data()))
|
||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||
->set("background", baton->tileBackground)
|
||||
->set("centre", baton->tileCentre)
|
||||
->set("id", const_cast<char*>(baton->tileId.data()))
|
||||
->set("skip_blanks", baton->tileSkipBlanks);
|
||||
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
|
||||
options->set("depth", baton->tileDepth);
|
||||
}
|
||||
if (!baton->tileBasename.empty()) {
|
||||
options->set("basename", const_cast<char*>(baton->tileBasename.data()));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/*
|
||||
Clear all thread-local data.
|
||||
*/
|
||||
@@ -1351,17 +1411,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
} else if (canvas == "ignore_aspect") {
|
||||
baton->canvas = sharp::Canvas::IGNORE_ASPECT;
|
||||
}
|
||||
// Tint chroma
|
||||
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
||||
baton->tintB = sharp::AttrAsDouble(options, "tintB");
|
||||
// Composite
|
||||
Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
|
||||
for (unsigned int i = 0; i < compositeArray.Length(); i++) {
|
||||
Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
|
||||
Composite *composite = new Composite;
|
||||
composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
|
||||
composite->mode = static_cast<VipsBlendMode>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
|
||||
composite->mode = sharp::AttrAsEnum<VipsBlendMode>(compositeObject, "blend", VIPS_TYPE_BLEND_MODE);
|
||||
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
||||
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
||||
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
||||
@@ -1375,7 +1431,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
|
||||
baton->position = sharp::AttrAsInt32(options, "position");
|
||||
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
||||
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
||||
baton->kernel = sharp::AttrAsEnum<VipsKernel>(options, "kernel", VIPS_TYPE_KERNEL);
|
||||
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
|
||||
// Join Channel Options
|
||||
if (options.Has("joinChannelIn")) {
|
||||
@@ -1397,17 +1453,23 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
||||
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
||||
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
||||
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
||||
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
|
||||
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
|
||||
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->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
|
||||
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
|
||||
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
||||
baton->gamma = sharp::AttrAsDouble(options, "gamma");
|
||||
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
|
||||
baton->linearA = sharp::AttrAsDouble(options, "linearA");
|
||||
baton->linearB = sharp::AttrAsDouble(options, "linearB");
|
||||
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
|
||||
baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
|
||||
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
|
||||
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
||||
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
||||
baton->tintB = sharp::AttrAsDouble(options, "tintB");
|
||||
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
||||
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
||||
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
||||
@@ -1435,10 +1497,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
||||
if (options.Has("boolean")) {
|
||||
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
|
||||
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
|
||||
baton->booleanOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN);
|
||||
}
|
||||
if (options.Has("bandBoolOp")) {
|
||||
baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
|
||||
baton->bandBoolOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN);
|
||||
}
|
||||
if (options.Has("convKernel")) {
|
||||
Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
|
||||
@@ -1460,11 +1522,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
||||
}
|
||||
}
|
||||
baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput"));
|
||||
baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
|
||||
options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
|
||||
if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
|
||||
baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
|
||||
}
|
||||
baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
|
||||
baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
|
||||
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
||||
baton->colourspace = VIPS_INTERPRETATION_sRGB;
|
||||
}
|
||||
@@ -1479,7 +1542,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
||||
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
||||
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
||||
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
||||
if (mdStrs.HasOwnProperty(k)) {
|
||||
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
||||
}
|
||||
}
|
||||
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
||||
// Format-specific
|
||||
@@ -1510,9 +1575,14 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
||||
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
||||
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
||||
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
||||
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
||||
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
||||
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
||||
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
||||
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
||||
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
||||
baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
|
||||
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
||||
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
||||
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
||||
@@ -1524,28 +1594,23 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
|
||||
baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
|
||||
}
|
||||
// tiff compression options
|
||||
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
|
||||
sharp::AttrAsStr(options, "tiffCompression").data()));
|
||||
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
||||
sharp::AttrAsStr(options, "tiffPredictor").data()));
|
||||
baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
|
||||
sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
|
||||
|
||||
baton->tiffCompression = sharp::AttrAsEnum<VipsForeignTiffCompression>(
|
||||
options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION);
|
||||
baton->tiffPredictor = sharp::AttrAsEnum<VipsForeignTiffPredictor>(
|
||||
options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR);
|
||||
baton->tiffResolutionUnit = sharp::AttrAsEnum<VipsForeignTiffResunit>(
|
||||
options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT);
|
||||
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
|
||||
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
|
||||
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||
sharp::AttrAsStr(options, "heifCompression").data()));
|
||||
baton->heifCompression = sharp::AttrAsEnum<VipsForeignHeifCompression>(
|
||||
options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
|
||||
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
||||
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
||||
// Raw output
|
||||
baton->rawDepth = static_cast<VipsBandFormat>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
||||
sharp::AttrAsStr(options, "rawDepth").data()));
|
||||
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
||||
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
||||
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
||||
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
|
||||
baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
|
||||
// Animated output properties
|
||||
if (sharp::HasAttr(options, "loop")) {
|
||||
baton->loop = sharp::AttrAsUint32(options, "loop");
|
||||
@@ -1553,24 +1618,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
if (sharp::HasAttr(options, "delay")) {
|
||||
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
||||
}
|
||||
// Tile output
|
||||
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
||||
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
||||
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
||||
baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
|
||||
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
|
||||
baton->tileContainer = static_cast<VipsForeignDzContainer>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
|
||||
sharp::AttrAsStr(options, "tileContainer").data()));
|
||||
baton->tileLayout = static_cast<VipsForeignDzLayout>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
|
||||
sharp::AttrAsStr(options, "tileLayout").data()));
|
||||
baton->tileContainer = sharp::AttrAsEnum<VipsForeignDzContainer>(
|
||||
options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER);
|
||||
baton->tileLayout = sharp::AttrAsEnum<VipsForeignDzLayout>(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT);
|
||||
baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
|
||||
baton->tileDepth = static_cast<VipsForeignDzDepth>(
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
||||
sharp::AttrAsStr(options, "tileDepth").data()));
|
||||
baton->tileDepth = sharp::AttrAsEnum<VipsForeignDzDepth>(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH);
|
||||
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
||||
baton->tileId = sharp::AttrAsStr(options, "tileId");
|
||||
baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
|
||||
|
||||
// Force random access for certain operations
|
||||
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
||||
|
||||
@@ -67,6 +67,7 @@ struct PipelineBaton {
|
||||
int width;
|
||||
int height;
|
||||
int channels;
|
||||
VipsKernel kernel;
|
||||
sharp::Canvas canvas;
|
||||
int position;
|
||||
std::vector<double> resizeBackground;
|
||||
@@ -75,7 +76,6 @@ struct PipelineBaton {
|
||||
int cropOffsetTop;
|
||||
bool premultiplied;
|
||||
bool tileCentre;
|
||||
std::string kernel;
|
||||
bool fastShrinkOnLoad;
|
||||
double tintA;
|
||||
double tintB;
|
||||
@@ -90,15 +90,19 @@ struct PipelineBaton {
|
||||
double lightness;
|
||||
int medianSize;
|
||||
double sharpenSigma;
|
||||
double sharpenFlat;
|
||||
double sharpenJagged;
|
||||
double sharpenM1;
|
||||
double sharpenM2;
|
||||
double sharpenX1;
|
||||
double sharpenY2;
|
||||
double sharpenY3;
|
||||
int threshold;
|
||||
bool thresholdGrayscale;
|
||||
std::vector<double> trimBackground;
|
||||
double trimThreshold;
|
||||
int trimOffsetLeft;
|
||||
int trimOffsetTop;
|
||||
double linearA;
|
||||
double linearB;
|
||||
std::vector<double> linearA;
|
||||
std::vector<double> linearB;
|
||||
double gamma;
|
||||
double gammaOut;
|
||||
bool greyscale;
|
||||
@@ -154,9 +158,14 @@ struct PipelineBaton {
|
||||
bool webpLossless;
|
||||
bool webpSmartSubsample;
|
||||
int webpEffort;
|
||||
bool webpMinSize;
|
||||
bool webpMixed;
|
||||
int gifBitdepth;
|
||||
int gifEffort;
|
||||
double gifDither;
|
||||
double gifInterFrameMaxError;
|
||||
double gifInterPaletteMaxError;
|
||||
bool gifReoptimise;
|
||||
int tiffQuality;
|
||||
VipsForeignTiffCompression tiffCompression;
|
||||
VipsForeignTiffPredictor tiffPredictor;
|
||||
@@ -173,6 +182,10 @@ struct PipelineBaton {
|
||||
int heifEffort;
|
||||
std::string heifChromaSubsampling;
|
||||
bool heifLossless;
|
||||
double jxlDistance;
|
||||
int jxlDecodingTier;
|
||||
int jxlEffort;
|
||||
bool jxlLossless;
|
||||
VipsBandFormat rawDepth;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
@@ -206,6 +219,7 @@ struct PipelineBaton {
|
||||
int tileSkipBlanks;
|
||||
VipsForeignDzDepth tileDepth;
|
||||
std::string tileId;
|
||||
std::string tileBasename;
|
||||
std::unique_ptr<double[]> recombMatrix;
|
||||
|
||||
PipelineBaton():
|
||||
@@ -214,6 +228,7 @@ struct PipelineBaton {
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
channels(0),
|
||||
kernel(VIPS_KERNEL_LANCZOS3),
|
||||
canvas(sharp::Canvas::CROP),
|
||||
position(0),
|
||||
resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
@@ -234,15 +249,19 @@ struct PipelineBaton {
|
||||
lightness(0),
|
||||
medianSize(0),
|
||||
sharpenSigma(0.0),
|
||||
sharpenFlat(1.0),
|
||||
sharpenJagged(2.0),
|
||||
sharpenM1(1.0),
|
||||
sharpenM2(2.0),
|
||||
sharpenX1(2.0),
|
||||
sharpenY2(10.0),
|
||||
sharpenY3(20.0),
|
||||
threshold(0),
|
||||
thresholdGrayscale(true),
|
||||
trimBackground{},
|
||||
trimThreshold(0.0),
|
||||
trimOffsetLeft(0),
|
||||
trimOffsetTop(0),
|
||||
linearA(1.0),
|
||||
linearB(0.0),
|
||||
linearA{},
|
||||
linearB{},
|
||||
gamma(0.0),
|
||||
greyscale(false),
|
||||
normalise(false),
|
||||
@@ -296,6 +315,14 @@ struct PipelineBaton {
|
||||
webpLossless(false),
|
||||
webpSmartSubsample(false),
|
||||
webpEffort(4),
|
||||
webpMinSize(false),
|
||||
webpMixed(false),
|
||||
gifBitdepth(8),
|
||||
gifEffort(7),
|
||||
gifDither(1.0),
|
||||
gifInterFrameMaxError(0.0),
|
||||
gifInterPaletteMaxError(3.0),
|
||||
gifReoptimise(false),
|
||||
tiffQuality(80),
|
||||
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
|
||||
@@ -312,6 +339,10 @@ struct PipelineBaton {
|
||||
heifEffort(4),
|
||||
heifChromaSubsampling("4:4:4"),
|
||||
heifLossless(false),
|
||||
jxlDistance(1.0),
|
||||
jxlDecodingTier(0),
|
||||
jxlEffort(7),
|
||||
jxlLossless(false),
|
||||
rawDepth(VIPS_FORMAT_UCHAR),
|
||||
withMetadata(false),
|
||||
withMetadataOrientation(-1),
|
||||
|
||||
17
src/sharp.cc
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <napi.h>
|
||||
#include <cstdlib>
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "common.h"
|
||||
@@ -22,16 +21,7 @@
|
||||
#include "utilities.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*) {
|
||||
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
||||
vips_init("sharp");
|
||||
return nullptr;
|
||||
}
|
||||
@@ -43,13 +33,6 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
|
||||
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
||||
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
|
||||
exports.Set("metadata", Napi::Function::New(env, metadata));
|
||||
exports.Set("pipeline", Napi::Function::New(env, pipeline));
|
||||
|
||||
@@ -176,6 +176,7 @@ Napi::Value stats(const Napi::CallbackInfo& info) {
|
||||
|
||||
// Input
|
||||
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
||||
baton->input->access = VIPS_ACCESS_RANDOM;
|
||||
|
||||
// Function to notify of libvips warnings
|
||||
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
||||
|
||||
@@ -115,17 +115,28 @@ Napi::Value format(const Napi::CallbackInfo& info) {
|
||||
Napi::Object format = Napi::Object::New(env);
|
||||
for (std::string const f : {
|
||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
||||
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k"
|
||||
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl"
|
||||
}) {
|
||||
// Input
|
||||
Napi::Boolean hasInputFile =
|
||||
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
|
||||
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
|
||||
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
|
||||
Napi::Boolean hasInputBuffer =
|
||||
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
|
||||
Napi::Object input = Napi::Object::New(env);
|
||||
input.Set("file", hasInputFile);
|
||||
input.Set("buffer", hasInputBuffer);
|
||||
input.Set("stream", hasInputBuffer);
|
||||
if (hasInputFile) {
|
||||
const VipsForeignClass *fc = VIPS_FOREIGN_CLASS(oc);
|
||||
if (fc->suffs) {
|
||||
Napi::Array fileSuffix = Napi::Array::New(env);
|
||||
const char **suffix = fc->suffs;
|
||||
for (int i = 0; *suffix; i++, suffix++) {
|
||||
fileSuffix.Set(i, Napi::String::New(env, *suffix));
|
||||
}
|
||||
input.Set("fileSuffix", fileSuffix);
|
||||
}
|
||||
}
|
||||
// Output
|
||||
Napi::Boolean hasOutputFile =
|
||||
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
|
||||
|
||||
21
test/beforeEach.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const sharp = require('../');
|
||||
|
||||
const usingCache = !process.env.G_DEBUG;
|
||||
const usingSimd = !process.env.VIPS_NOVECTOR;
|
||||
const concurrency = Number(process.env.VIPS_CONCURRENCY) || 0;
|
||||
|
||||
exports.mochaHooks = {
|
||||
beforeEach () {
|
||||
sharp.cache(usingCache);
|
||||
sharp.simd(usingSimd);
|
||||
sharp.concurrency(concurrency);
|
||||
},
|
||||
|
||||
afterEach () {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
}
|
||||
};
|
||||
26
test/bench/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM ubuntu:22.04
|
||||
ARG BRANCH=main
|
||||
|
||||
# Install basic dependencies
|
||||
RUN apt-get -y update && apt-get install -y build-essential curl git
|
||||
|
||||
# Install latest Node.js LTS
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install benchmark dependencies
|
||||
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
|
||||
|
||||
# Install sharp
|
||||
RUN mkdir /tmp/sharp
|
||||
RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git
|
||||
RUN cd /tmp/sharp && npm install --build-from-source
|
||||
|
||||
# Install benchmark test
|
||||
RUN cd /tmp/sharp/test/bench && npm install --omit optional
|
||||
|
||||
RUN cat /etc/os-release | grep VERSION=
|
||||
RUN node -v
|
||||
|
||||
WORKDIR /tmp/sharp/test/bench
|
||||
CMD [ "node", "perf" ]
|
||||
@@ -7,16 +7,19 @@
|
||||
"scripts": {
|
||||
"test": "node perf && node random && node parallel"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dependencies": {
|
||||
"@squoosh/cli": "0.7.2",
|
||||
"@squoosh/lib": "0.4.0",
|
||||
"async": "3.2.3",
|
||||
"async": "3.2.4",
|
||||
"benchmark": "2.1.4",
|
||||
"gm": "1.23.1",
|
||||
"gm": "1.25.0",
|
||||
"imagemagick": "0.1.3",
|
||||
"jimp": "0.16.1",
|
||||
"mapnik": "4.5.9",
|
||||
"semver": "7.3.5"
|
||||
"jimp": "0.16.2",
|
||||
"semver": "7.3.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-node": "4.1.0",
|
||||
"mapnik": "4.5.9"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
|
||||
@@ -5,16 +5,24 @@ const fs = require('fs');
|
||||
const { exec } = require('child_process');
|
||||
|
||||
const async = require('async');
|
||||
const assert = require('assert');
|
||||
const Benchmark = require('benchmark');
|
||||
|
||||
const safeRequire = (name) => {
|
||||
try {
|
||||
return require(name);
|
||||
} catch (err) {}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Contenders
|
||||
const sharp = require('../../');
|
||||
const gm = require('gm');
|
||||
const imagemagick = require('imagemagick');
|
||||
const mapnik = require('mapnik');
|
||||
const mapnik = safeRequire('mapnik');
|
||||
const jimp = require('jimp');
|
||||
const squoosh = require('@squoosh/lib');
|
||||
process.env.TF_CPP_MIN_LOG_LEVEL = 1;
|
||||
const tfjs = safeRequire('@tensorflow/tfjs-node');
|
||||
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
@@ -137,7 +145,7 @@ async.series({
|
||||
}
|
||||
});
|
||||
// mapnik
|
||||
jpegSuite.add('mapnik-file-file', {
|
||||
mapnik && jpegSuite.add('mapnik-file-file', {
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
mapnik.Image.open(fixtures.inputJpg, function (err, img) {
|
||||
@@ -212,11 +220,10 @@ async.series({
|
||||
.filter('Lanczos')
|
||||
.resize(width, height)
|
||||
.quality(80)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -243,16 +250,33 @@ async.series({
|
||||
.filter('Lanczos')
|
||||
.resize(width, height)
|
||||
.quality(80)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// tfjs
|
||||
tfjs && jpegSuite.add('tfjs-node-buffer-buffer', {
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
const decoded = tfjs.node.decodeJpeg(inputJpgBuffer);
|
||||
const resized = tfjs.image.resizeBilinear(decoded, [height, width]);
|
||||
tfjs
|
||||
.node
|
||||
.encodeJpeg(resized, 'rgb', 80)
|
||||
.then(function () {
|
||||
deferred.resolve();
|
||||
tfjs.disposeVariables();
|
||||
})
|
||||
.catch(function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
});
|
||||
// sharp
|
||||
jpegSuite.add('sharp-buffer-file', {
|
||||
defer: true,
|
||||
@@ -272,11 +296,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -311,11 +334,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -326,8 +348,7 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.toBuffer()
|
||||
.then(function (buffer) {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
.then(function () {
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
@@ -350,11 +371,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.sharpen()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -365,11 +385,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.sharpen(3, 1, 3)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -380,11 +399,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.blur()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -395,11 +413,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.blur(3)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -410,11 +427,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.gamma()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -425,11 +441,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.normalise()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -440,11 +455,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.greyscale()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -456,11 +470,10 @@ async.series({
|
||||
.resize(width, height)
|
||||
.gamma()
|
||||
.greyscale()
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -471,11 +484,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.jpeg({ progressive: true })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -486,11 +498,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -501,11 +512,10 @@ async.series({
|
||||
sharp(inputJpgBuffer)
|
||||
.rotate(90)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -516,12 +526,11 @@ async.series({
|
||||
sharp.simd(false);
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
sharp.simd(true);
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -531,11 +540,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputJpgBuffer, { sequentialRead: true })
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -548,11 +556,10 @@ async.series({
|
||||
fit: 'cover',
|
||||
position: sharp.strategy.entropy
|
||||
})
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -565,11 +572,10 @@ async.series({
|
||||
fit: 'cover',
|
||||
position: sharp.strategy.attention
|
||||
})
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -588,11 +594,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height, { kernel: 'cubic' })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -602,11 +607,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height, { kernel: 'lanczos2' })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -616,11 +620,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputJpgBuffer)
|
||||
.resize(width, height, { kernel: 'lanczos3' })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -681,7 +684,7 @@ async.series({
|
||||
}
|
||||
});
|
||||
// mapnik
|
||||
pngSuite.add('mapnik-file-file', {
|
||||
mapnik && pngSuite.add('mapnik-file-file', {
|
||||
defer: true,
|
||||
fn: function (deferred) {
|
||||
mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
|
||||
@@ -774,11 +777,10 @@ async.series({
|
||||
.resize(width, height)
|
||||
.define('PNG:compression-level=6')
|
||||
.define('PNG:compression-filter=0')
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -807,11 +809,10 @@ async.series({
|
||||
sharp(inputPngBuffer)
|
||||
.resize(width, height)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -838,11 +839,10 @@ async.series({
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
|
||||
.resize(width, height)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -854,11 +854,10 @@ async.series({
|
||||
sharp(inputPngBuffer)
|
||||
.resize(width, height)
|
||||
.png({ compressionLevel: 6, progressive: true })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -870,11 +869,10 @@ async.series({
|
||||
sharp(inputPngBuffer)
|
||||
.resize(width, height)
|
||||
.png({ adaptiveFiltering: true, compressionLevel: 6 })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -886,11 +884,10 @@ async.series({
|
||||
sharp(inputPngBuffer)
|
||||
.resize(width, height)
|
||||
.png({ compressionLevel: 9 })
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -923,11 +920,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(inputWebPBuffer)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -950,11 +946,10 @@ async.series({
|
||||
fn: function (deferred) {
|
||||
sharp(fixtures.inputWebP)
|
||||
.resize(width, height)
|
||||
.toBuffer(function (err, buffer) {
|
||||
.toBuffer(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
@@ -966,7 +961,9 @@ async.series({
|
||||
}).run();
|
||||
}
|
||||
}, function (err, results) {
|
||||
assert(!err, err);
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
Object.keys(results).forEach(function (format) {
|
||||
if (results[format].toString().substr(0, 5) !== 'sharp') {
|
||||
console.log('sharp was slower than ' + results[format] + ' for ' + format);
|
||||
|
||||
13
test/bench/run-with-docker.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if ! type docker >/dev/null; then
|
||||
echo "Please install docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BRANCH=$(git branch --show-current)
|
||||
echo "Running sharp performance tests using $BRANCH branch"
|
||||
|
||||
docker build --build-arg "BRANCH=$BRANCH" -t sharp-test-bench .
|
||||
docker run --rm -it sharp-test-bench
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
CPPFLAGS="--coverage" LDFLAGS="--coverage" npm rebuild
|
||||
npm test
|
||||
geninfo --no-external --base-directory src --output-file coverage/sharp.info build/Release/obj.target/sharp/src
|
||||
genhtml --title sharp --demangle-cpp --output-directory coverage/sharp coverage/*.info
|
||||
BIN
test/fixtures/65536-uint32-limit.png
vendored
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
test/fixtures/Flag_of_the_Netherlands-16bit.png
vendored
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
test/fixtures/Flag_of_the_Netherlands-alpha.png
vendored
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
test/fixtures/Flag_of_the_Netherlands.png
vendored
Normal file
|
After Width: | Height: | Size: 794 B |
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/embed-lab-into-rgba.png
vendored
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.2 KiB |
BIN
test/fixtures/expected/extract-lch.jpg
vendored
|
Before Width: | Height: | Size: 13 KiB |
BIN
test/fixtures/expected/extract-rotate-extract.jpg
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
test/fixtures/expected/flatten-orange.jpg
vendored
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 6.4 KiB |
BIN
test/fixtures/expected/hilutite.jpg
vendored
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 424 KiB |
BIN
test/fixtures/expected/linear-per-channel.jpg
vendored
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
test/fixtures/expected/rotate-extract-45.jpg
vendored
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
5
test/fixtures/index.js
vendored
@@ -94,6 +94,11 @@ module.exports = {
|
||||
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
|
||||
inputPngP3: getPath('p3.png'), // https://github.com/lovell/sharp/issues/2862
|
||||
inputPngPalette: getPath('swiss.png'), // https://github.com/randy408/libspng/issues/188
|
||||
inputPngTrimIncludeAlpha: getPath('trim-mc.png'), // https://github.com/lovell/sharp/issues/2166
|
||||
inputPngTrimSpecificColour: getPath('Flag_of_the_Netherlands.png'), // https://commons.wikimedia.org/wiki/File:Flag_of_the_Netherlands.svg
|
||||
inputPngTrimSpecificColour16bit: getPath('Flag_of_the_Netherlands-16bit.png'), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png
|
||||
inputPngTrimSpecificColourIncludeAlpha: getPath('Flag_of_the_Netherlands-alpha.png'), // convert Flag_of_the_Netherlands.png -alpha set -background none -channel A -evaluate multiply 0.5 +channel Flag_of_the_Netherlands-alpha.png
|
||||
inputPngUint32Limit: getPath('65536-uint32-limit.png'), // https://alexandre.alapetite.fr/doc-alex/large-image/
|
||||
|
||||
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||
|
||||
BIN
test/fixtures/trim-mc.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -8,7 +8,7 @@ fi
|
||||
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
||||
|
||||
for test in ./test/unit/*.js; do
|
||||
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 valgrind \
|
||||
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 VIPS_NOVECTOR=1 valgrind \
|
||||
--suppressions=test/leak/libvips.supp \
|
||||
--suppressions=test/leak/sharp.supp \
|
||||
--gen-suppressions=yes \
|
||||
@@ -16,5 +16,5 @@ for test in ./test/unit/*.js; do
|
||||
--show-leak-kinds=definite,indirect,possible \
|
||||
--num-callers=20 \
|
||||
--trace-children=yes \
|
||||
node --expose-gc node_modules/.bin/mocha --slow=60000 --timeout=120000 --file test/unit/beforeEach.js "$test";
|
||||
node --expose-gc node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "$test";
|
||||
done
|
||||
|
||||
@@ -232,6 +232,14 @@
|
||||
fun:XML_ParseBuffer
|
||||
obj:*/libfontconfig.so.*
|
||||
}
|
||||
{
|
||||
leak_fontconfig_XML_ParseBuffer_indirect
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: indirect
|
||||
...
|
||||
fun:XML_ParseBuffer
|
||||
obj:*/libfontconfig.so.*
|
||||
}
|
||||
{
|
||||
leak_fontconfig_FcInitLoadConfigAndFonts
|
||||
Memcheck:Leak
|
||||
@@ -242,6 +250,36 @@
|
||||
...
|
||||
fun:FcInitLoadConfigAndFonts
|
||||
}
|
||||
{
|
||||
leak_fontconfig_FcDefaultSubstitute
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: indirect
|
||||
fun:calloc
|
||||
...
|
||||
fun:FcDefaultSubstitute
|
||||
...
|
||||
fun:pango_itemize_with_base_dir
|
||||
...
|
||||
fun:pango_layout_get_pixel_extents
|
||||
fun:vips_text_get_extents
|
||||
}
|
||||
{
|
||||
leak_fontconfig_FcLangSetCreate
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: indirect
|
||||
fun:malloc
|
||||
fun:FcLangSetCreate
|
||||
fun:FcLangSetCopy
|
||||
fun:FcValueSave
|
||||
...
|
||||
fun:FcFontRenderPrepare
|
||||
fun:FcFontMatch
|
||||
...
|
||||
fun:pango_itemize_with_base_dir
|
||||
...
|
||||
fun:pango_layout_get_pixel_extents
|
||||
fun:vips_text_get_extents
|
||||
}
|
||||
|
||||
# heif
|
||||
{
|
||||
@@ -311,6 +349,13 @@
|
||||
fun:heif_context_read_from_reader
|
||||
}
|
||||
|
||||
# orc
|
||||
{
|
||||
addr_orcexec
|
||||
Memcheck:Addr1
|
||||
obj:*/orcexec.*
|
||||
}
|
||||
|
||||
# libvips
|
||||
{
|
||||
cond_libvips_interpolate_lbb
|
||||
@@ -907,3 +952,13 @@
|
||||
fun:_ZN4node7binding6DLOpenERKN2v820FunctionCallbackInfoINS1_5ValueEEE
|
||||
fun:_ZN2v88internal12_GLOBAL__N_119HandleApiCallHelperILb0EEENS0_11MaybeHandleINS0_6ObjectEEEPNS0_7IsolateENS0_6HandleINS0_10HeapObjectEEESA_NS8_INS0_20FunctionTemplateInfoEEENS8_IS4_EENS0_16BuiltinArgumentsE
|
||||
}
|
||||
{
|
||||
addr_node_binding_dlopen_strncmp
|
||||
Memcheck:Addr8
|
||||
fun:strncmp
|
||||
fun:is_dst
|
||||
...
|
||||
fun:dlopen_implementation
|
||||
...
|
||||
fun:_ZNSt17_Function_handlerIFbPN4node7binding4DLibEEZNS1_6DLOpenERKN2v820FunctionCallbackInfoINS5_5ValueEEEEUlS3_E_E9_M_invokeERKSt9_Any_dataOS3_
|
||||
}
|
||||
|
||||
@@ -10,34 +10,40 @@ describe('HTTP agent', function () {
|
||||
|
||||
it('HTTPS proxy with auth from HTTPS_PROXY', function () {
|
||||
process.env.HTTPS_PROXY = 'https://user:pass@secure:123';
|
||||
const proxy = agent();
|
||||
let logMsg = '';
|
||||
const proxy = agent(msg => { logMsg = msg; });
|
||||
delete process.env.HTTPS_PROXY;
|
||||
assert.strictEqual('object', typeof proxy);
|
||||
assert.strictEqual('secure', proxy.options.proxy.host);
|
||||
assert.strictEqual(123, proxy.options.proxy.port);
|
||||
assert.strictEqual('user:pass', proxy.options.proxy.proxyAuth);
|
||||
assert.strictEqual(443, proxy.defaultPort);
|
||||
assert.strictEqual(logMsg, 'Via proxy https:://secure:123 with credentials');
|
||||
});
|
||||
|
||||
it('HTTPS proxy with auth from HTTPS_PROXY using credentials containing special characters', function () {
|
||||
process.env.HTTPS_PROXY = 'https://user,:pass=@secure:123';
|
||||
const proxy = agent();
|
||||
process.env.HTTPS_PROXY = 'https://user,:pass=@secure:789';
|
||||
let logMsg = '';
|
||||
const proxy = agent(msg => { logMsg = msg; });
|
||||
delete process.env.HTTPS_PROXY;
|
||||
assert.strictEqual('object', typeof proxy);
|
||||
assert.strictEqual('secure', proxy.options.proxy.host);
|
||||
assert.strictEqual(123, proxy.options.proxy.port);
|
||||
assert.strictEqual(789, proxy.options.proxy.port);
|
||||
assert.strictEqual('user,:pass=', proxy.options.proxy.proxyAuth);
|
||||
assert.strictEqual(443, proxy.defaultPort);
|
||||
assert.strictEqual(logMsg, 'Via proxy https:://secure:789 with credentials');
|
||||
});
|
||||
|
||||
it('HTTP proxy without auth from npm_config_proxy', function () {
|
||||
process.env.npm_config_proxy = 'http://plaintext:456';
|
||||
const proxy = agent();
|
||||
let logMsg = '';
|
||||
const proxy = agent(msg => { logMsg = msg; });
|
||||
delete process.env.npm_config_proxy;
|
||||
assert.strictEqual('object', typeof proxy);
|
||||
assert.strictEqual('plaintext', proxy.options.proxy.host);
|
||||
assert.strictEqual(456, proxy.options.proxy.port);
|
||||
assert.strictEqual(null, proxy.options.proxy.proxyAuth);
|
||||
assert.strictEqual(443, proxy.defaultPort);
|
||||
assert.strictEqual(logMsg, 'Via proxy http:://plaintext:456 no credentials');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('Alpha transparency', function () {
|
||||
.flatten({
|
||||
background: { r: 255, g: 102, b: 0 }
|
||||
})
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(400, info.width);
|
||||
@@ -35,6 +36,7 @@ describe('Alpha transparency', function () {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.resize(400, 300)
|
||||
.flatten({ background: '#ff6600' })
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(400, info.width);
|
||||
|
||||
@@ -27,7 +27,9 @@ describe('AVIF', () => {
|
||||
format: 'jpeg',
|
||||
hasAlpha: false,
|
||||
hasProfile: false,
|
||||
height: 14,
|
||||
// 32 / (2048 / 858) = 13.40625
|
||||
// Math.round(13.40625) = 13
|
||||
height: 13,
|
||||
isProgressive: false,
|
||||
space: 'srgb',
|
||||
width: 32
|
||||
@@ -70,7 +72,7 @@ describe('AVIF', () => {
|
||||
format: 'heif',
|
||||
hasAlpha: false,
|
||||
hasProfile: false,
|
||||
height: 14,
|
||||
height: 13,
|
||||
isProgressive: false,
|
||||
pagePrimary: 0,
|
||||
pages: 1,
|
||||
@@ -101,4 +103,28 @@ describe('AVIF', () => {
|
||||
width: 10
|
||||
});
|
||||
});
|
||||
|
||||
it('should cast to uchar', async () => {
|
||||
const data = await sharp(inputJpg)
|
||||
.resize(32)
|
||||
.sharpen()
|
||||
.avif({ effort: 0 })
|
||||
.toBuffer();
|
||||
const { size, ...metadata } = await sharp(data)
|
||||
.metadata();
|
||||
assert.deepStrictEqual(metadata, {
|
||||
channels: 3,
|
||||
compression: 'av1',
|
||||
depth: 'uchar',
|
||||
format: 'heif',
|
||||
hasAlpha: false,
|
||||
hasProfile: false,
|
||||
height: 26,
|
||||
isProgressive: false,
|
||||
pagePrimary: 0,
|
||||
pages: 1,
|
||||
space: 'srgb',
|
||||
width: 32
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const detectLibc = require('detect-libc');
|
||||
const sharp = require('../../');
|
||||
|
||||
const libcFamily = detectLibc.familySync();
|
||||
const usingCache = libcFamily !== detectLibc.MUSL;
|
||||
const usingSimd = !process.env.G_DEBUG;
|
||||
const concurrency =
|
||||
libcFamily === detectLibc.MUSL || process.arch === 'arm'
|
||||
? 1
|
||||
: undefined;
|
||||
|
||||
beforeEach(function () {
|
||||
sharp.cache(usingCache);
|
||||
sharp.simd(usingSimd);
|
||||
sharp.concurrency(concurrency);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
});
|
||||
@@ -45,22 +45,20 @@ const blends = [
|
||||
|
||||
// Test
|
||||
describe('composite', () => {
|
||||
it('blend', () => Promise.all(
|
||||
blends.map(blend => {
|
||||
blends.forEach(blend => {
|
||||
it(`blend ${blend}`, async () => {
|
||||
const filename = `composite.blend.${blend}.png`;
|
||||
const actual = fixtures.path(`output.${filename}`);
|
||||
const expected = fixtures.expected(filename);
|
||||
return sharp(redRect)
|
||||
await sharp(redRect)
|
||||
.composite([{
|
||||
input: blueRect,
|
||||
blend
|
||||
}])
|
||||
.toFile(actual)
|
||||
.then(() => {
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
})
|
||||
));
|
||||
.toFile(actual);
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('premultiplied true', () => {
|
||||
const filename = 'composite.premultiplied.png';
|
||||
@@ -121,11 +119,11 @@ describe('composite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple', () => {
|
||||
it('multiple', async () => {
|
||||
const filename = 'composite-multiple.png';
|
||||
const actual = fixtures.path(`output.${filename}`);
|
||||
const expected = fixtures.expected(filename);
|
||||
return sharp(redRect)
|
||||
await sharp(redRect)
|
||||
.composite([{
|
||||
input: blueRect,
|
||||
gravity: 'northeast'
|
||||
@@ -133,10 +131,8 @@ describe('composite', () => {
|
||||
input: greenRect,
|
||||
gravity: 'southwest'
|
||||
}])
|
||||
.toFile(actual)
|
||||
.then(() => {
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
.toFile(actual);
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
|
||||
it('zero offset', done => {
|
||||
@@ -424,4 +420,53 @@ describe('composite', () => {
|
||||
|
||||
assert.deepStrictEqual(red, { r, g, b });
|
||||
});
|
||||
|
||||
it('Ensure tiled composition works with resized fit=outside', async () => {
|
||||
const { info } = await sharp({
|
||||
create: {
|
||||
width: 41, height: 41, channels: 3, background: 'red'
|
||||
}
|
||||
})
|
||||
.resize({
|
||||
width: 10,
|
||||
height: 40,
|
||||
fit: 'outside'
|
||||
})
|
||||
.composite([
|
||||
{
|
||||
input: {
|
||||
create: {
|
||||
width: 16, height: 16, channels: 3, background: 'green'
|
||||
}
|
||||
},
|
||||
tile: true
|
||||
}
|
||||
])
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
assert.strictEqual(info.width, 40);
|
||||
assert.strictEqual(info.height, 40);
|
||||
});
|
||||
|
||||
it('Ensure implict unpremultiply after resize but before composite', async () => {
|
||||
const [r, g, b, a] = await sharp({
|
||||
create: {
|
||||
width: 1, height: 1, channels: 4, background: 'saddlebrown'
|
||||
}
|
||||
})
|
||||
.resize({ width: 8 })
|
||||
.composite([{
|
||||
input: Buffer.from([255, 255, 255, 128]),
|
||||
raw: { width: 1, height: 1, channels: 4 },
|
||||
tile: true,
|
||||
blend: 'dest-in'
|
||||
}])
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.strictEqual(r, 139);
|
||||
assert.strictEqual(g, 69);
|
||||
assert.strictEqual(b, 19);
|
||||
assert.strictEqual(a, 128);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('Extend', function () {
|
||||
});
|
||||
|
||||
it('Premultiply background when compositing', async () => {
|
||||
const background = '#bf1942cc';
|
||||
const background = { r: 191, g: 25, b: 66, alpha: 0.8 };
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 1, height: 1, channels: 4, background: '#fff0'
|
||||
@@ -158,10 +158,6 @@ describe('Extend', function () {
|
||||
})
|
||||
.raw()
|
||||
.toBuffer();
|
||||
const [r1, g1, b1, a1, r2, g2, b2, a2] = data;
|
||||
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);
|
||||
assert.deepStrictEqual(Array.from(data), [191, 25, 66, 204, 191, 25, 66, 204]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,20 @@ describe('Partial image extraction', function () {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(280, info.width);
|
||||
assert.strictEqual(380, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-extract.jpg'), data, { threshold: 7 }, done);
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-extract.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Extract then rotate then extract', function (done) {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.extract({ left: 20, top: 10, width: 180, height: 280 })
|
||||
.rotate(90)
|
||||
.extract({ left: 20, top: 10, width: 200, height: 100 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(200, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract-rotate-extract.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -164,18 +177,55 @@ describe('Partial image extraction', function () {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(380, info.width);
|
||||
assert.strictEqual(280, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-extract-45.jpg'), data, { threshold: 7 }, done);
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-extract-45.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Rotate with EXIF mirroring then extract', function (done) {
|
||||
sharp(fixtures.inputJpgWithLandscapeExif7)
|
||||
.rotate()
|
||||
.extract({ left: 0, top: 208, width: 60, height: 40 })
|
||||
.toBuffer(function (err, data) {
|
||||
if (err) throw err;
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done);
|
||||
describe('Apply exif orientation and mirroring then extract', () => {
|
||||
[
|
||||
{
|
||||
name: 'EXIF-1',
|
||||
image: fixtures.inputJpgWithLandscapeExif1
|
||||
},
|
||||
{
|
||||
name: 'EXIF-2',
|
||||
image: fixtures.inputJpgWithLandscapeExif2
|
||||
},
|
||||
{
|
||||
name: 'EXIF-3',
|
||||
image: fixtures.inputJpgWithLandscapeExif3
|
||||
},
|
||||
{
|
||||
name: 'EXIF-4',
|
||||
image: fixtures.inputJpgWithLandscapeExif4
|
||||
},
|
||||
{
|
||||
name: 'EXIF-5',
|
||||
image: fixtures.inputJpgWithLandscapeExif5
|
||||
},
|
||||
{
|
||||
name: 'EXIF-6',
|
||||
image: fixtures.inputJpgWithLandscapeExif6
|
||||
},
|
||||
{
|
||||
name: 'EXIF-7',
|
||||
image: fixtures.inputJpgWithLandscapeExif7
|
||||
},
|
||||
{
|
||||
name: 'EXIF-8',
|
||||
image: fixtures.inputJpgWithLandscapeExif8
|
||||
}
|
||||
].forEach(({ name, image }) => {
|
||||
it(name, function (done) {
|
||||
sharp(image)
|
||||
.rotate()
|
||||
.extract({ left: 0, top: 208, width: 60, height: 40 })
|
||||
.toBuffer(function (err, data) {
|
||||
if (err) throw err;
|
||||
fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
@@ -244,5 +294,27 @@ describe('Partial image extraction', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Multiple extract emits warning', () => {
|
||||
let warningMessage = '';
|
||||
const s = sharp();
|
||||
s.on('warning', function (msg) { warningMessage = msg; });
|
||||
const options = { top: 0, left: 0, width: 1, height: 1 };
|
||||
s.extract(options).extract(options);
|
||||
assert.strictEqual(warningMessage, '');
|
||||
s.extract(options);
|
||||
assert.strictEqual(warningMessage, 'ignoring previous extract options');
|
||||
});
|
||||
|
||||
it('Multiple rotate+extract emits warning', () => {
|
||||
let warningMessage = '';
|
||||
const s = sharp().rotate();
|
||||
s.on('warning', function (msg) { warningMessage = msg; });
|
||||
const options = { top: 0, left: 0, width: 1, height: 1 };
|
||||
s.extract(options).extract(options);
|
||||
assert.strictEqual(warningMessage, '');
|
||||
s.extract(options);
|
||||
assert.strictEqual(warningMessage, 'ignoring previous extract options');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,19 +54,13 @@ describe('Image channel extraction', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('With colorspace conversion', function (done) {
|
||||
const output = fixtures.path('output.extract-lch.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.extractChannel(1)
|
||||
it('With colorspace conversion', async () => {
|
||||
const [chroma] = await sharp({ create: { width: 1, height: 1, channels: 3, background: 'red' } })
|
||||
.toColourspace('lch')
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('extract-lch.jpg'), 9);
|
||||
done();
|
||||
});
|
||||
.extractChannel(1)
|
||||
.toBuffer();
|
||||
|
||||
assert.strictEqual(chroma, 104);
|
||||
});
|
||||
|
||||
it('Alpha from 16-bit PNG', function (done) {
|
||||
@@ -108,12 +102,12 @@ describe('Image channel extraction', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Non-existent channel', function (done) {
|
||||
sharp(fixtures.inputPng)
|
||||
.extractChannel(1)
|
||||
.toBuffer(function (err) {
|
||||
assert(err instanceof Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('Non-existent channel', async () =>
|
||||
await assert.rejects(
|
||||
() => sharp({ create: { width: 1, height: 1, channels: 3, background: 'red' } })
|
||||
.extractChannel(3)
|
||||
.toBuffer(),
|
||||
/Cannot extract channel 3 from image with channels 0-2/
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
104
test/unit/failOn.js
Normal file
@@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const sharp = require('../../lib');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('failOn', () => {
|
||||
it('handles truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOn: 'none' })
|
||||
.resize(32, 24)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('truncated.jpg'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles truncated PNG, emits warnings', function (done) {
|
||||
let isWarningEmitted = false;
|
||||
sharp(fixtures.inputPngTruncated, { failOn: 'none' })
|
||||
.on('warning', function (warning) {
|
||||
assert.ok(
|
||||
['read gave 2 warnings', 'not enough data', 'end of stream']
|
||||
.some(m => warning.includes(m)));
|
||||
isWarningEmitted = true;
|
||||
})
|
||||
.resize(32, 24)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, isWarningEmitted);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('throws for invalid options', () => {
|
||||
assert.throws(
|
||||
() => sharp({ failOn: 'zoinks' }),
|
||||
/Expected one of: none, truncated, error, warning for failOn but received zoinks of type string/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ failOn: 1 }),
|
||||
/Expected one of: none, truncated, error, warning for failOn but received 1 of type number/
|
||||
);
|
||||
});
|
||||
|
||||
it('deprecated failOnError', () => {
|
||||
assert.doesNotThrow(
|
||||
() => sharp({ failOnError: true })
|
||||
);
|
||||
assert.doesNotThrow(
|
||||
() => sharp({ failOnError: false })
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ failOnError: 'zoinks' }),
|
||||
/Expected boolean for failOnError but received zoinks of type string/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ failOnError: 1 }),
|
||||
/Expected boolean for failOnError but received 1 of type number/
|
||||
);
|
||||
});
|
||||
|
||||
it('returns errors to callback for truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOn: 'truncated' }).toBuffer(function (err, data, info) {
|
||||
assert.ok(err.message.includes('VipsJpeg: Premature end of'), err);
|
||||
assert.strictEqual(data, undefined);
|
||||
assert.strictEqual(info, undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns errors to callback for truncated PNG', function (done) {
|
||||
sharp(fixtures.inputPngTruncated, { failOn: 'truncated' }).toBuffer(function (err, data, info) {
|
||||
assert.ok(err.message.includes('read error'), err);
|
||||
assert.strictEqual(data, undefined);
|
||||
assert.strictEqual(info, undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects promises for truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOn: 'error' })
|
||||
.toBuffer()
|
||||
.then(() => {
|
||||
throw new Error('Expected rejection');
|
||||
})
|
||||
.catch(err => {
|
||||
done(err.message.includes('VipsJpeg: Premature end of') ? undefined : err);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles stream-based input', async () => {
|
||||
const writable = sharp({ failOn: 'none' }).resize(32, 24);
|
||||
fs.createReadStream(fixtures.inputJpgTruncated).pipe(writable);
|
||||
return writable.toBuffer();
|
||||
});
|
||||
});
|
||||