Compare commits
174 Commits
v0.33.0-al
...
v0.33.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc32e0bd3f | ||
|
|
0546e48467 | ||
|
|
ab65b7a0f1 | ||
|
|
2474bd4163 | ||
|
|
ff2e689d35 | ||
|
|
6327f13717 | ||
|
|
f1e69a218e | ||
|
|
3c14dbb21e | ||
|
|
82cebc31d0 | ||
|
|
ad36fa0605 | ||
|
|
de42667767 | ||
|
|
2eb03b0049 | ||
|
|
f7ed9b7fb6 | ||
|
|
7fbb988180 | ||
|
|
490210fc60 | ||
|
|
735fee74db | ||
|
|
67a5854b89 | ||
|
|
f128ebdbd4 | ||
|
|
2672de2480 | ||
|
|
67a4592756 | ||
|
|
10c6f474d9 | ||
|
|
d642108be2 | ||
|
|
c2a024101b | ||
|
|
2f0bbebfc9 | ||
|
|
60c5c5083d | ||
|
|
eab7dc1b49 | ||
|
|
5c7f37a0e0 | ||
|
|
ae06f46914 | ||
|
|
9c05ea8dd2 | ||
|
|
472aaf3311 | ||
|
|
56fae3eda1 | ||
|
|
cc96c21e42 | ||
|
|
1d344888ec | ||
|
|
bee235ee76 | ||
|
|
19d0e272e6 | ||
|
|
16b764f1c1 | ||
|
|
1593ee3838 | ||
|
|
668b5ba8bc | ||
|
|
29336f4cc7 | ||
|
|
b5fddd7c5e | ||
|
|
da655a1859 | ||
|
|
93c615d39f | ||
|
|
f325dc3ec9 | ||
|
|
0fde71c783 | ||
|
|
9c8fbaa1cc | ||
|
|
a9e662e612 | ||
|
|
ba843002be | ||
|
|
7f3d452bc5 | ||
|
|
aa8bc19362 | ||
|
|
36e60bf040 | ||
|
|
a1309aa3b8 | ||
|
|
3e8a0fc522 | ||
|
|
397ee492d9 | ||
|
|
52b9dc0f63 | ||
|
|
a715c73fc2 | ||
|
|
579cf93030 | ||
|
|
f67228e5ea | ||
|
|
6257994746 | ||
|
|
7950fc0ea3 | ||
|
|
fc93ab3b82 | ||
|
|
0981b24f60 | ||
|
|
02fd565476 | ||
|
|
e55bb93b10 | ||
|
|
55466f122c | ||
|
|
ede8217ab3 | ||
|
|
2689fb4e65 | ||
|
|
eaf31a59e5 | ||
|
|
aa1bbcb5c1 | ||
|
|
3c26080c39 | ||
|
|
dc07fd4e9c | ||
|
|
7bc74feb11 | ||
|
|
c5f318ed4d | ||
|
|
8fbb1cd154 | ||
|
|
88aee8a887 | ||
|
|
0f77b18078 | ||
|
|
3eeaee71c0 | ||
|
|
045d54e2e6 | ||
|
|
75fedf1b75 | ||
|
|
debdacb726 | ||
|
|
1c8ae67ed2 | ||
|
|
0eb57698ec | ||
|
|
bc95531f2d | ||
|
|
60f4048d6c | ||
|
|
fb70fbb09f | ||
|
|
fc439bedf1 | ||
|
|
26d0b7147d | ||
|
|
af6aa8a690 | ||
|
|
bd4f1abba2 | ||
|
|
d2656a3679 | ||
|
|
a3b45ceccc | ||
|
|
0dcc7d50a8 | ||
|
|
bcb22af034 | ||
|
|
d04dc62666 | ||
|
|
c30d355f97 | ||
|
|
49cb148b38 | ||
|
|
3bc31a8b20 | ||
|
|
c28523e70e | ||
|
|
278f393f74 | ||
|
|
cbf68c1395 | ||
|
|
45e8071599 | ||
|
|
b96389d975 | ||
|
|
a77ac6ae25 | ||
|
|
9bcf399b4c | ||
|
|
4aacee8055 | ||
|
|
0b18aeff62 | ||
|
|
bed1c2ac18 | ||
|
|
8cd832656b | ||
|
|
0499f59e71 | ||
|
|
1fa59bf9b3 | ||
|
|
db40ee6912 | ||
|
|
02b98b8e1b | ||
|
|
31fef216e4 | ||
|
|
77ab5d7a51 | ||
|
|
cd5cf7ce2d | ||
|
|
39cb9d9a6c | ||
|
|
4919bc5134 | ||
|
|
a4e64eb01f | ||
|
|
328b18df88 | ||
|
|
5e7bf32e5e | ||
|
|
04403f4e5f | ||
|
|
420e0822b4 | ||
|
|
f7a3ea6415 | ||
|
|
f28e79ef4f | ||
|
|
5cd787bf85 | ||
|
|
021d637fd6 | ||
|
|
2e14096af7 | ||
|
|
fe2b298a2f | ||
|
|
c9e3996007 | ||
|
|
3a0c375692 | ||
|
|
c5eaeb2ddb | ||
|
|
19fa4cd1d3 | ||
|
|
0adf7ef16f | ||
|
|
8f7fb96a44 | ||
|
|
9e3b021b1a | ||
|
|
25164d4cef | ||
|
|
516b1ec332 | ||
|
|
95ba045a69 | ||
|
|
6e02f9288e | ||
|
|
a584ae093e | ||
|
|
1592f96b7b | ||
|
|
004fff975f | ||
|
|
4d049ee8f5 | ||
|
|
c80e92fa16 | ||
|
|
545e09fad2 | ||
|
|
133dc56ff4 | ||
|
|
9c877d93fa | ||
|
|
7ad86fed03 | ||
|
|
31cf07f0ba | ||
|
|
4ffb48711a | ||
|
|
3da96a86e6 | ||
|
|
45ed9ea9bf | ||
|
|
61057f25bc | ||
|
|
68ef72cb61 | ||
|
|
ef32dccb83 | ||
|
|
e78200cc84 | ||
|
|
3f7313d031 | ||
|
|
139e4b9f65 | ||
|
|
9680f00ddd | ||
|
|
0b0ebfe6f8 | ||
|
|
5aadb8294c | ||
|
|
ace1681886 | ||
|
|
75ef61a958 | ||
|
|
2dba5b5451 | ||
|
|
a8f68ba7f0 | ||
|
|
475bf16b09 | ||
|
|
67f49a8dcd | ||
|
|
074004392c | ||
|
|
8c5a493f68 | ||
|
|
cccd1635c5 | ||
|
|
1915c1387e | ||
|
|
4a37a27cca | ||
|
|
ab8a4ed532 | ||
|
|
12fd512b83 | ||
|
|
239435a6dc |
@@ -24,7 +24,7 @@ jobs:
|
|||||||
linux-arm64-glibc-node-18:
|
linux-arm64-glibc-node-18:
|
||||||
resource_class: arm.medium
|
resource_class: arm.medium
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:current
|
image: ubuntu-2204:current
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: |
|
- run: |
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
linux-arm64-glibc-node-20:
|
linux-arm64-glibc-node-20:
|
||||||
resource_class: arm.medium
|
resource_class: arm.medium
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:current
|
image: ubuntu-2204:current
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: |
|
- run: |
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
linux-arm64-musl-node-18:
|
linux-arm64-musl-node-18:
|
||||||
resource_class: arm.medium
|
resource_class: arm.medium
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:current
|
image: ubuntu-2204:current
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: |
|
- run: |
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
linux-arm64-musl-node-20:
|
linux-arm64-musl-node-20:
|
||||||
resource_class: arm.medium
|
resource_class: arm.medium
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2004:current
|
image: ubuntu-2204:current
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: |
|
- run: |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-13-2
|
image_family: freebsd-14-0-snap
|
||||||
|
|
||||||
task:
|
task:
|
||||||
name: FreeBSD
|
name: FreeBSD
|
||||||
@@ -10,7 +10,8 @@ task:
|
|||||||
- pkg update -f
|
- pkg update -f
|
||||||
- pkg upgrade -y
|
- pkg upgrade -y
|
||||||
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
|
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
|
||||||
|
- pkg-config --modversion vips-cpp
|
||||||
install_script:
|
install_script:
|
||||||
- npm install --build-from-source
|
- npm install --build-from-source
|
||||||
test_script:
|
test_script:
|
||||||
- npm test
|
- npx mocha --no-config --spec=test/unit/io.js --timeout=30000
|
||||||
|
|||||||
2
.github/CONTRIBUTING.md
vendored
@@ -63,7 +63,7 @@ By way of example, the `background()` method present in v0.20.0 was deprecated i
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The public API is documented with [JSDoc](http://usejsdoc.org/) annotated comments.
|
The public API is documented with [JSDoc](https://jsdoc.app/) annotated comments.
|
||||||
|
|
||||||
These can be converted to Markdown by running:
|
These can be converted to Markdown by running:
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
33
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -11,7 +11,10 @@ labels: installation
|
|||||||
|
|
||||||
<!-- Please place an [x] in the box to confirm. -->
|
<!-- Please place an [x] in the box to confirm. -->
|
||||||
|
|
||||||
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
|
- [ ] I have read and understood all of the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
|
||||||
|
- [ ] I have searched for known bugs relating to this problem in my choice of package manager.
|
||||||
|
|
||||||
|
You must confirm both of these before continuing.
|
||||||
|
|
||||||
### Are you using the latest version of sharp?
|
### Are you using the latest version of sharp?
|
||||||
|
|
||||||
@@ -21,27 +24,37 @@ labels: installation
|
|||||||
|
|
||||||
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
|
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.
|
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.
|
||||||
|
|
||||||
### Are you using a supported runtime?
|
### Are you using a supported runtime?
|
||||||
|
|
||||||
<!-- Please place an [x] in the relevant box to confirm. -->
|
<!-- Please place an [x] in the relevant box to confirm. -->
|
||||||
|
|
||||||
- [ ] I am using Node.js 18 with a version >= 18.17.0
|
- [ ] I am using Node.js with a version that satisfies `^18.17.0 || ^20.3.0 || >=21.0.0`
|
||||||
- [ ] I am using Node.js 20 with a version >= 20.3.0
|
- [ ] I am using Deno
|
||||||
- [ ] I am using Node.js 21 or later
|
- [ ] I am using Bun
|
||||||
|
|
||||||
If you cannot confirm any of these, please upgrade to the latest version and try again before opening an issue.
|
If you cannot confirm any of these,
|
||||||
|
please upgrade to the latest version
|
||||||
|
and try again before opening an issue.
|
||||||
|
|
||||||
### Are you using a supported package manager?
|
### Are you using a supported package manager and installing optional dependencies?
|
||||||
|
|
||||||
<!-- Please place an [x] in the relevant box to confirm. -->
|
<!-- Please place an [x] in the relevant box to confirm. -->
|
||||||
|
|
||||||
- [ ] I am using npm >= 9.6.5
|
- [ ] I am using npm >= 10.1.0 with `--include=optional`
|
||||||
- [ ] I am using yarn >= 3.2.0
|
- [ ] I am using yarn >= 3.2.0
|
||||||
- [ ] I am using pnpm >= 7.1.0
|
- [ ] I am using pnpm >= 7.1.0 with `--no-optional=false`
|
||||||
|
- [ ] I am using Deno
|
||||||
|
- [ ] I am using Bun
|
||||||
|
|
||||||
If you cannot confirm any of these, please upgrade to the latest version and try again before opening an issue.
|
If you cannot confirm any of these, please upgrade to the latest version of your chosen package manager
|
||||||
|
and ensure you are allowing the installation of optional or multi-platform dependencies before opening an issue.
|
||||||
|
|
||||||
|
### What is the complete error message, including the full stack trace?
|
||||||
|
|
||||||
|
<!-- Please provide the error message and stack trace here. -->
|
||||||
|
|
||||||
### What is the complete output of running `npm install --verbose --foreground-scripts sharp` in an empty directory?
|
### What is the complete output of running `npm install --verbose --foreground-scripts sharp` in an empty directory?
|
||||||
|
|
||||||
|
|||||||
16
.github/ISSUE_TEMPLATE/possible-bug.md
vendored
@@ -32,6 +32,22 @@ If you are using another package which depends on a version of `sharp` that is n
|
|||||||
|
|
||||||
<!-- Please provide output of the above command here. -->
|
<!-- Please provide output of the above command here. -->
|
||||||
|
|
||||||
|
### Does this problem relate to file caching?
|
||||||
|
|
||||||
|
The default behaviour of libvips is to cache input files, which can lead to `EBUSY` or `EPERM` errors on Windows.
|
||||||
|
Use [`sharp.cache(false)`](https://sharp.pixelplumbing.com/api-utility#cache) to switch this feature off.
|
||||||
|
|
||||||
|
- [ ] Adding `sharp.cache(false)` does not fix this problem.
|
||||||
|
|
||||||
|
### Does this problem relate to images appearing to have been rotated by 90 degrees?
|
||||||
|
|
||||||
|
Images that contain EXIF Orientation metadata are not auto-oriented. By default, EXIF metadata is removed.
|
||||||
|
|
||||||
|
- To auto-orient pixel values use the parameter-less [`rotate()`](https://sharp.pixelplumbing.com/api-operation#rotate) operation.
|
||||||
|
- To retain EXIF Orientation use [`keepExif()`](https://sharp.pixelplumbing.com/api-output#keepexif).
|
||||||
|
|
||||||
|
- [ ] Using `rotate()` or `keepExif()` does not fix this problem.
|
||||||
|
|
||||||
### What are the steps to reproduce?
|
### What are the steps to reproduce?
|
||||||
|
|
||||||
<!-- Please enter steps to reproduce here. -->
|
<!-- Please enter steps to reproduce here. -->
|
||||||
|
|||||||
102
.github/workflows/ci.yml
vendored
@@ -16,12 +16,14 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-22.04
|
||||||
container: rockylinux:8
|
container: rockylinux:8
|
||||||
|
nodejs_arch: x64
|
||||||
nodejs_version: "^18.17.0"
|
nodejs_version: "^18.17.0"
|
||||||
nodejs_version_major: 18
|
nodejs_version_major: 18
|
||||||
platform: linux-x64
|
platform: linux-x64
|
||||||
prebuild: true
|
prebuild: true
|
||||||
- os: ubuntu-22.04
|
- os: ubuntu-22.04
|
||||||
container: rockylinux:8
|
container: rockylinux:8
|
||||||
|
nodejs_arch: x64
|
||||||
nodejs_version: "^20.3.0"
|
nodejs_version: "^20.3.0"
|
||||||
nodejs_version_major: 20
|
nodejs_version_major: 20
|
||||||
platform: linux-x64
|
platform: linux-x64
|
||||||
@@ -34,20 +36,31 @@ jobs:
|
|||||||
container: node:20-alpine3.18
|
container: node:20-alpine3.18
|
||||||
nodejs_version_major: 20
|
nodejs_version_major: 20
|
||||||
platform: linuxmusl-x64
|
platform: linuxmusl-x64
|
||||||
- os: macos-11
|
- os: macos-12
|
||||||
nodejs_arch: x64
|
nodejs_arch: x64
|
||||||
nodejs_version: "^18.17.0"
|
nodejs_version: "^18.17.0"
|
||||||
nodejs_version_major: 18
|
nodejs_version_major: 18
|
||||||
platform: darwin-x64
|
platform: darwin-x64
|
||||||
prebuild: true
|
prebuild: true
|
||||||
- os: macos-11
|
- os: macos-12
|
||||||
nodejs_arch: x64
|
nodejs_arch: x64
|
||||||
nodejs_version: "^20.3.0"
|
nodejs_version: "^20.3.0"
|
||||||
nodejs_version_major: 20
|
nodejs_version_major: 20
|
||||||
platform: darwin-x64
|
platform: darwin-x64
|
||||||
|
- os: macos-14
|
||||||
|
nodejs_arch: arm64
|
||||||
|
nodejs_version: "^18.17.0"
|
||||||
|
nodejs_version_major: 18
|
||||||
|
platform: darwin-arm64
|
||||||
|
prebuild: true
|
||||||
|
- os: macos-14
|
||||||
|
nodejs_arch: arm64
|
||||||
|
nodejs_version: "^20.3.0"
|
||||||
|
nodejs_version_major: 20
|
||||||
|
platform: darwin-arm64
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
nodejs_arch: x86
|
nodejs_arch: x86
|
||||||
nodejs_version: "^18.17.0"
|
nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10
|
||||||
nodejs_version_major: 18
|
nodejs_version_major: 18
|
||||||
platform: win32-ia32
|
platform: win32-ia32
|
||||||
prebuild: true
|
prebuild: true
|
||||||
@@ -68,32 +81,22 @@ jobs:
|
|||||||
nodejs_version_major: 20
|
nodejs_version_major: 20
|
||||||
platform: win32-x64
|
platform: win32-x64
|
||||||
steps:
|
steps:
|
||||||
- name: Dependencies (Linux glibc)
|
|
||||||
if: contains(matrix.container, 'centos')
|
|
||||||
run: |
|
|
||||||
yum install -y https://rpm.nodesource.com/pub_${{ matrix.nodejs_version_major }}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm
|
|
||||||
yum install -y https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
|
|
||||||
yum install -y centos-release-scl
|
|
||||||
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)
|
- name: Dependencies (Rocky Linux glibc)
|
||||||
if: contains(matrix.container, 'rockylinux')
|
if: contains(matrix.container, 'rockylinux')
|
||||||
run: |
|
run: |
|
||||||
dnf install -y https://rpm.nodesource.com/pub_${{ matrix.nodejs_version_major }}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm
|
dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts
|
||||||
dnf install -y --setopt=nodesource-nodejs.module_hotfixes=1 nodejs
|
|
||||||
dnf install -y gcc-toolset-11-gcc-c++ make git python3 fontconfig google-noto-sans-fonts
|
|
||||||
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
|
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
|
||||||
- name: Dependencies (Linux musl)
|
- name: Dependencies (Linux musl)
|
||||||
if: contains(matrix.container, 'alpine')
|
if: contains(matrix.container, 'alpine')
|
||||||
run: apk add build-base git python3 font-noto --update-cache
|
run: apk add build-base git python3 font-noto --update-cache
|
||||||
- name: Dependencies (Python 3.11 - macOS, Windows)
|
- name: Dependencies (Python 3.11 - macOS, Windows)
|
||||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: "3.12"
|
||||||
- name: Dependencies (Node.js - macOS, Windows)
|
- name: Dependencies (Node.js)
|
||||||
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
if: "!contains(matrix.platform, 'linuxmusl')"
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.nodejs_version }}
|
node-version: ${{ matrix.nodejs_version }}
|
||||||
architecture: ${{ matrix.nodejs_arch }}
|
architecture: ${{ matrix.nodejs_arch }}
|
||||||
@@ -128,12 +131,14 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- platform: linux-arm
|
- platform: linux-arm
|
||||||
|
distro: bullseye
|
||||||
run_on_arch: armv6
|
run_on_arch: armv6
|
||||||
nodejs_arch: armv6l
|
nodejs_arch: armv6l
|
||||||
nodejs_hostname: unofficial-builds.nodejs.org
|
nodejs_hostname: unofficial-builds.nodejs.org
|
||||||
nodejs_version: "18.17.0"
|
nodejs_version: "18.17.0"
|
||||||
nodejs_version_major: 18
|
nodejs_version_major: 18
|
||||||
- platform: linux-s390x
|
- platform: linux-s390x
|
||||||
|
distro: bullseye
|
||||||
run_on_arch: s390x
|
run_on_arch: s390x
|
||||||
nodejs_arch: s390x
|
nodejs_arch: s390x
|
||||||
nodejs_hostname: nodejs.org
|
nodejs_hostname: nodejs.org
|
||||||
@@ -144,7 +149,7 @@ jobs:
|
|||||||
- uses: uraimo/run-on-arch-action@v2
|
- uses: uraimo/run-on-arch-action@v2
|
||||||
with:
|
with:
|
||||||
arch: ${{ matrix.run_on_arch }}
|
arch: ${{ matrix.run_on_arch }}
|
||||||
distro: buster
|
distro: ${{ matrix.distro }}
|
||||||
env: |
|
env: |
|
||||||
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
|
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
|
||||||
run: |
|
run: |
|
||||||
@@ -161,48 +166,43 @@ jobs:
|
|||||||
npm install --ignore-scripts
|
npm install --ignore-scripts
|
||||||
npx mocha --no-config --spec=test/unit/io.js --timeout=30000
|
npx mocha --no-config --spec=test/unit/io.js --timeout=30000
|
||||||
[[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true
|
[[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true
|
||||||
macstadium-runner:
|
github-runner-emscripten:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }}
|
name: wasm32 - prebuild
|
||||||
runs-on: macos-m1
|
runs-on: ubuntu-22.04
|
||||||
strategy:
|
container: "emscripten/emsdk:3.1.64"
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- nodejs_arch: x64
|
|
||||||
nodejs_version: "^18.17.0"
|
|
||||||
nodejs_version_major: 18
|
|
||||||
platform: darwin-x64
|
|
||||||
- nodejs_arch: arm64
|
|
||||||
nodejs_version: "^18.17.0"
|
|
||||||
nodejs_version_major: 18
|
|
||||||
platform: darwin-arm64
|
|
||||||
prebuild: true
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Dependencies (Node.js)
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.nodejs_version }}
|
|
||||||
architecture: ${{ matrix.nodejs_arch }}
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Dependencies
|
||||||
|
run: apt-get update && apt-get install -y pkg-config
|
||||||
|
- name: Dependencies (Node.js)
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
- name: Install
|
- name: Install
|
||||||
run: npm install --build-from-source
|
run: emmake npm install --build-from-source
|
||||||
|
- name: Verify emscripten versions match
|
||||||
|
run: |
|
||||||
|
EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten")
|
||||||
|
EMSCRIPTEN_VERSION_SHARP=$(emcc -dumpversion)
|
||||||
|
echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS"
|
||||||
|
echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP"
|
||||||
|
test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
|
||||||
- name: Test
|
- name: Test
|
||||||
run: npm test
|
run: emmake npm test
|
||||||
- name: Test packaging
|
- name: Test packaging
|
||||||
run: |
|
run: |
|
||||||
npm run package-from-local-build
|
emmake npm run package-from-local-build
|
||||||
npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
|
npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32"
|
||||||
npm run clean
|
npm run clean
|
||||||
npm install --ignore-scripts
|
rm -rf node_modules/@img/sharp-linux-x64
|
||||||
|
npm install --cpu=wasm32
|
||||||
npm test
|
npm test
|
||||||
- name: Prebuild
|
- name: Prebuild
|
||||||
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
env:
|
env:
|
||||||
|
npm_config_nodedir: emscripten
|
||||||
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: cd src && ln -s ../package.json && npx prebuild
|
run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0
|
||||||
|
|||||||
80
.github/workflows/npm.yml
vendored
@@ -27,6 +27,14 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
runtime: node
|
runtime: node
|
||||||
package-manager: yarn
|
package-manager: yarn
|
||||||
|
- name: linux-x64-node-yarn-pnp
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-pnp
|
||||||
|
- name: linux-x64-node-yarn-v1
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-v1
|
||||||
- name: linux-x64-deno
|
- name: linux-x64-deno
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
runtime: deno
|
runtime: deno
|
||||||
@@ -35,22 +43,30 @@ jobs:
|
|||||||
runtime: bun
|
runtime: bun
|
||||||
|
|
||||||
- name: darwin-x64-node-npm
|
- name: darwin-x64-node-npm
|
||||||
runs-on: macos-11
|
runs-on: macos-12
|
||||||
runtime: node
|
runtime: node
|
||||||
package-manager: npm
|
package-manager: npm
|
||||||
- name: darwin-x64-node-pnpm
|
- name: darwin-x64-node-pnpm
|
||||||
runs-on: macos-11
|
runs-on: macos-12
|
||||||
runtime: node
|
runtime: node
|
||||||
package-manager: pnpm
|
package-manager: pnpm
|
||||||
- name: darwin-x64-node-yarn
|
- name: darwin-x64-node-yarn
|
||||||
runs-on: macos-11
|
runs-on: macos-12
|
||||||
runtime: node
|
runtime: node
|
||||||
package-manager: yarn
|
package-manager: yarn
|
||||||
|
- name: darwin-x64-node-yarn-pnp
|
||||||
|
runs-on: macos-12
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-pnp
|
||||||
|
- name: darwin-x64-node-yarn-v1
|
||||||
|
runs-on: macos-12
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-v1
|
||||||
- name: darwin-x64-deno
|
- name: darwin-x64-deno
|
||||||
runs-on: macos-11
|
runs-on: macos-12
|
||||||
runtime: deno
|
runtime: deno
|
||||||
- name: darwin-x64-bun
|
- name: darwin-x64-bun
|
||||||
runs-on: macos-11
|
runs-on: macos-12
|
||||||
runtime: bun
|
runtime: bun
|
||||||
|
|
||||||
- name: win32-x64-node-npm
|
- name: win32-x64-node-npm
|
||||||
@@ -65,6 +81,14 @@ jobs:
|
|||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
runtime: node
|
runtime: node
|
||||||
package-manager: yarn
|
package-manager: yarn
|
||||||
|
- name: win32-x64-node-yarn-pnp
|
||||||
|
runs-on: windows-2019
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-pnp
|
||||||
|
- name: win32-x64-node-yarn-v1
|
||||||
|
runs-on: windows-2019
|
||||||
|
runtime: node
|
||||||
|
package-manager: yarn-v1
|
||||||
- name: win32-x64-deno
|
- name: win32-x64-deno
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
runtime: deno
|
runtime: deno
|
||||||
@@ -72,12 +96,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
if: ${{ matrix.runtime == 'node' }}
|
if: ${{ matrix.runtime == 'node' }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
if: ${{ matrix.package-manager == 'pnpm' }}
|
if: ${{ matrix.package-manager == 'pnpm' }}
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
- name: Install Deno
|
- name: Install Deno
|
||||||
@@ -87,51 +111,50 @@ jobs:
|
|||||||
deno-version: v1.x
|
deno-version: v1.x
|
||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
if: ${{ matrix.runtime == 'bun' }}
|
if: ${{ matrix.runtime == 'bun' }}
|
||||||
uses: oven-sh/setup-bun@v1
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: Version
|
- name: Version
|
||||||
id: version
|
id: version
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
core.setOutput('semver', context.ref.replace('refs/tags/v',''))
|
core.setOutput('semver', context.ref.replace('refs/tags/v',''))
|
||||||
- name: Create package.json
|
- name: Create package.json
|
||||||
uses: DamianReeves/write-file-action@v1.2
|
uses: DamianReeves/write-file-action@v1.3
|
||||||
with:
|
with:
|
||||||
path: package.json
|
path: package.json
|
||||||
contents: |
|
contents: |
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sharp": "${{ steps.version.outputs.semver }}"
|
"sharp": "${{ steps.version.outputs.semver }}"
|
||||||
}
|
},
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
- name: Create release.mjs
|
- name: Create release.mjs
|
||||||
uses: DamianReeves/write-file-action@v1.2
|
uses: DamianReeves/write-file-action@v1.3
|
||||||
with:
|
with:
|
||||||
path: release.mjs
|
path: release.mjs
|
||||||
contents: |
|
contents: |
|
||||||
import { createRequire } from 'node:module';
|
|
||||||
import { deepStrictEqual } from 'node:assert';
|
import { deepStrictEqual } from 'node:assert';
|
||||||
const require = createRequire(import.meta.url);
|
import sharp from 'sharp';
|
||||||
const sharp = require('sharp');
|
deepStrictEqual(['.jpg', '.jpeg', '.jpe', '.jfif'], sharp.format.jpeg.input.fileSuffix);
|
||||||
deepStrictEqual(['.jpg', '.jpeg', '.jpe'], sharp.format.jpeg.input.fileSuffix);
|
|
||||||
|
|
||||||
- name: Run with Node.js + npm
|
- name: Run with Node.js + npm
|
||||||
if: ${{ matrix.runtime == 'node' && matrix.package-manager == 'npm' }}
|
if: ${{ matrix.package-manager == 'npm' }}
|
||||||
run: |
|
run: |
|
||||||
npm install --ignore-scripts
|
npm install --ignore-scripts
|
||||||
node release.mjs
|
node release.mjs
|
||||||
|
|
||||||
- name: Run with Node.js + pnpm
|
- name: Run with Node.js + pnpm
|
||||||
if: ${{ matrix.runtime == 'node' && matrix.package-manager == 'pnpm' }}
|
if: ${{ matrix.package-manager == 'pnpm' }}
|
||||||
run: |
|
run: |
|
||||||
pnpm install --ignore-scripts
|
pnpm install --ignore-scripts
|
||||||
node release.mjs
|
node release.mjs
|
||||||
|
|
||||||
- name: Run with Node.js + yarn
|
- name: Run with Node.js + yarn
|
||||||
if: ${{ matrix.runtime == 'node' && matrix.package-manager == 'yarn' }}
|
if: ${{ matrix.package-manager == 'yarn' }}
|
||||||
run: |
|
run: |
|
||||||
corepack enable
|
corepack enable
|
||||||
yarn set version stable
|
yarn set version stable
|
||||||
@@ -141,6 +164,25 @@ jobs:
|
|||||||
yarn install
|
yarn install
|
||||||
node release.mjs
|
node release.mjs
|
||||||
|
|
||||||
|
- name: Run with Node.js + yarn pnp
|
||||||
|
if: ${{ matrix.package-manager == 'yarn-pnp' }}
|
||||||
|
run: |
|
||||||
|
corepack enable
|
||||||
|
yarn set version stable
|
||||||
|
yarn config set enableImmutableInstalls false
|
||||||
|
yarn config set enableScripts false
|
||||||
|
yarn config set nodeLinker pnp
|
||||||
|
yarn install
|
||||||
|
yarn node release.mjs
|
||||||
|
|
||||||
|
- name: Run with Node.js + yarn v1
|
||||||
|
if: ${{ matrix.package-manager == 'yarn-v1' }}
|
||||||
|
run: |
|
||||||
|
corepack enable
|
||||||
|
yarn set version classic
|
||||||
|
yarn install
|
||||||
|
node release.mjs
|
||||||
|
|
||||||
- name: Run with Deno
|
- name: Run with Deno
|
||||||
if: ${{ matrix.runtime == 'deno' }}
|
if: ${{ matrix.runtime == 'deno' }}
|
||||||
run: deno run --allow-read --allow-ffi release.mjs
|
run: deno run --allow-read --allow-ffi release.mjs
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions
|
|||||||
|
|
||||||
It can be used with all JavaScript runtimes
|
It can be used with all JavaScript runtimes
|
||||||
that provide support for Node-API v9, including
|
that provide support for Node-API v9, including
|
||||||
Node.js >= 18.17.0, Deno and Bun.
|
Node.js (^18.17.0 or >= 20.3.0), Deno and Bun.
|
||||||
|
|
||||||
Resizing an image is typically 4x-5x faster than using the
|
Resizing an image is typically 4x-5x faster than using the
|
||||||
quickest ImageMagick and GraphicsMagick settings
|
quickest ImageMagick and GraphicsMagick settings
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## tint
|
## tint
|
||||||
> tint(rgb) ⇒ <code>Sharp</code>
|
> tint(tint) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
Tint the image using the provided chroma while preserving the image luminance.
|
Tint the image using the provided colour.
|
||||||
An alpha channel may be present and will be unchanged by the operation.
|
An alpha channel may be present and will be unchanged by the operation.
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ An alpha channel may be present and will be unchanged by the operation.
|
|||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| rgb | <code>string</code> \| <code>Object</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. |
|
| tint | <code>string</code> \| <code>Object</code> | Parsed by the [color](https://www.npmjs.org/package/color) module. |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -66,8 +66,6 @@ The input image will be converted to the provided colourspace at the start of th
|
|||||||
All operations will use this colourspace before converting to the output colourspace,
|
All operations will use this colourspace before converting to the output colourspace,
|
||||||
as defined by [toColourspace](#tocolourspace).
|
as defined by [toColourspace](#tocolourspace).
|
||||||
|
|
||||||
This feature is experimental and has not yet been fully-tested with all operations.
|
|
||||||
|
|
||||||
|
|
||||||
**Throws**:
|
**Throws**:
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ Non-critical problems encountered during processing are emitted as `warning` eve
|
|||||||
|
|
||||||
Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
|
|
||||||
|
When loading more than one page/frame of an animated image,
|
||||||
|
these are combined as a vertically-stacked "toilet roll" image
|
||||||
|
where the overall height is the `pageHeight` multiplied by the number of `pages`.
|
||||||
|
|
||||||
**Throws**:
|
**Throws**:
|
||||||
|
|
||||||
- <code>Error</code> Invalid parameters
|
- <code>Error</code> Invalid parameters
|
||||||
@@ -62,7 +66,7 @@ Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_st
|
|||||||
| [options.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
|
| [options.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
|
||||||
| [options.text.rgba] | <code>boolean</code> | <code>false</code> | 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>`. |
|
| [options.text.rgba] | <code>boolean</code> | <code>false</code> | 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>`. |
|
||||||
| [options.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
|
| [options.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
|
||||||
| [options.text.wrap] | <code>string</code> | <code>"'word'"</code> | word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'. |
|
| [options.text.wrap] | <code>string</code> | <code>"'word'"</code> | word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'. |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -75,14 +79,16 @@ sharp('input.jpg')
|
|||||||
```
|
```
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
// Read image data from readableStream,
|
// Read image data from remote URL,
|
||||||
// resize to 300 pixels wide,
|
// resize to 300 pixels wide,
|
||||||
// emit an 'info' event with calculated dimensions
|
// emit an 'info' event with calculated dimensions
|
||||||
// and finally write image data to writableStream
|
// and finally write image data to writableStream
|
||||||
var transformer = sharp()
|
const { body } = fetch('https://...');
|
||||||
|
const readableStream = Readable.fromWeb(body);
|
||||||
|
const transformer = sharp()
|
||||||
.resize(300)
|
.resize(300)
|
||||||
.on('info', function(info) {
|
.on('info', ({ height }) => {
|
||||||
console.log('Image height is ' + info.height);
|
console.log(`Image height is ${height}`);
|
||||||
});
|
});
|
||||||
readableStream.pipe(transformer).pipe(writableStream);
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ A `Promise` is returned when `callback` is not provided.
|
|||||||
- `xmp`: Buffer containing raw XMP 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
|
||||||
- `formatMagick`: String containing format for images loaded via *magick
|
- `formatMagick`: String containing format for images loaded via *magick
|
||||||
|
- `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if
|
|||||||
Only one rotation can occur per pipeline.
|
Only one rotation can occur per pipeline.
|
||||||
Previous calls to `rotate` in the same pipeline will be ignored.
|
Previous calls to `rotate` in the same pipeline will be ignored.
|
||||||
|
|
||||||
|
Multi-page images can only be rotated by 180 degrees.
|
||||||
|
|
||||||
Method order is important when rotating, resizing and/or extracting regions,
|
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)`.
|
for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ const output = await sharp(input).median(5).toBuffer();
|
|||||||
|
|
||||||
|
|
||||||
## blur
|
## blur
|
||||||
> blur([sigma]) ⇒ <code>Sharp</code>
|
> blur([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
Blur the image.
|
Blur the image.
|
||||||
|
|
||||||
@@ -243,9 +245,12 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
|||||||
- <code>Error</code> Invalid parameters
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Default | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
|
| [options] | <code>Object</code> \| <code>number</code> \| <code>Boolean</code> | | |
|
||||||
|
| [options.sigma] | <code>number</code> | | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
|
||||||
|
| [options.precision] | <code>string</code> | <code>"'integer'"</code> | How accurate the operation should be, one of: integer, float, approximate. |
|
||||||
|
| [options.minAmplitude] | <code>number</code> | <code>0.2</code> | A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask. |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -578,7 +583,7 @@ Recombine the image with the specified matrix.
|
|||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| inputMatrix | <code>Array.<Array.<number>></code> | 3x3 Recombination matrix |
|
| inputMatrix | <code>Array.<Array.<number>></code> | 3x3 or 4x4 Recombination matrix |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ A `Promise` is returned when `callback` is not provided.
|
|||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| fileOut | <code>string</code> | the path to write the image data to. |
|
| fileOut | <code>string</code> | the path to write the image data to. |
|
||||||
| [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. |
|
| [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. Animated output will also contain `pageHeight` and `pages`. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -59,6 +59,7 @@ See [withMetadata](#withmetadata) for control over this.
|
|||||||
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||||
`channels` and `premultiplied` (indicating if premultiplication was used).
|
`channels` and `premultiplied` (indicating if premultiplication was used).
|
||||||
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||||
|
Animated output will also contain `pageHeight` and `pages`.
|
||||||
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
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.
|
A `Promise` is returned when `callback` is not provided.
|
||||||
@@ -111,17 +112,157 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## keepExif
|
||||||
|
> keepExif() ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Keep all EXIF metadata from the input image in the output image.
|
||||||
|
|
||||||
|
EXIF metadata is unsupported for TIFF output.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const outputWithExif = await sharp(inputWithExif)
|
||||||
|
.keepExif()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## withExif
|
||||||
|
> withExif(exif) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const dataWithExif = await sharp(input)
|
||||||
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'The National Gallery'
|
||||||
|
},
|
||||||
|
IFD3: {
|
||||||
|
GPSLatitudeRef: 'N',
|
||||||
|
GPSLatitude: '51/1 30/1 3230/100',
|
||||||
|
GPSLongitudeRef: 'W',
|
||||||
|
GPSLongitude: '0/1 7/1 4366/100'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## withExifMerge
|
||||||
|
> withExifMerge(exif) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Update EXIF metadata from the input image in the output image.
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const dataWithMergedExif = await sharp(inputWithExif)
|
||||||
|
.withExifMerge({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'The National Gallery'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## keepIccProfile
|
||||||
|
> keepIccProfile() ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Keep ICC profile from the input image in the output image.
|
||||||
|
|
||||||
|
Where necessary, will attempt to convert the output colour space to match the profile.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||||
|
.keepIccProfile()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## withIccProfile
|
||||||
|
> withIccProfile(icc, [options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Transform using an ICC profile and attach to the output image.
|
||||||
|
|
||||||
|
This can either be an absolute filesystem path or
|
||||||
|
built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||||
|
|
||||||
|
|
||||||
|
**Throws**:
|
||||||
|
|
||||||
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
|
||||||
|
| Param | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| icc | <code>string</code> | | Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk). |
|
||||||
|
| [options] | <code>Object</code> | | |
|
||||||
|
| [options.attach] | <code>number</code> | <code>true</code> | Should the ICC profile be included in the output image metadata? |
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const outputWithP3 = await sharp(input)
|
||||||
|
.withIccProfile('p3')
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## keepMetadata
|
||||||
|
> keepMetadata() ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
|
Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||||
|
|
||||||
|
The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||||
|
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||||
|
|
||||||
|
|
||||||
|
**Since**: 0.33.0
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
const outputWithMetadata = await sharp(inputWithMetadata)
|
||||||
|
.keepMetadata()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## withMetadata
|
## withMetadata
|
||||||
> withMetadata([options]) ⇒ <code>Sharp</code>
|
> withMetadata([options]) ⇒ <code>Sharp</code>
|
||||||
|
|
||||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||||
This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
|
||||||
unless a custom output profile is provided.
|
|
||||||
|
|
||||||
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
|
||||||
|
|
||||||
EXIF metadata is unsupported for TIFF output.
|
Allows orientation and density to be set or updated.
|
||||||
|
|
||||||
|
|
||||||
**Throws**:
|
**Throws**:
|
||||||
@@ -129,38 +270,16 @@ EXIF metadata is unsupported for TIFF output.
|
|||||||
- <code>Error</code> Invalid parameters
|
- <code>Error</code> Invalid parameters
|
||||||
|
|
||||||
|
|
||||||
| Param | Type | Default | Description |
|
| Param | Type | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [options] | <code>Object</code> | | |
|
| [options] | <code>Object</code> | |
|
||||||
| [options.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
|
| [options.orientation] | <code>number</code> | Used to update the EXIF `Orientation` tag, integer between 1 and 8. |
|
||||||
| [options.icc] | <code>string</code> | <code>"'srgb'"</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
|
| [options.density] | <code>number</code> | Number of pixels per inch (DPI). |
|
||||||
| [options.exif] | <code>Object.<Object></code> | <code>{}</code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
|
||||||
| [options.density] | <code>number</code> | | Number of pixels per inch (DPI). |
|
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
sharp('input.jpg')
|
const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||||
.withMetadata()
|
.withMetadata()
|
||||||
.toFile('output-with-metadata.jpg')
|
|
||||||
.then(info => { ... });
|
|
||||||
```
|
|
||||||
**Example**
|
|
||||||
```js
|
|
||||||
// Set output EXIF metadata
|
|
||||||
const data = await sharp(input)
|
|
||||||
.withMetadata({
|
|
||||||
exif: {
|
|
||||||
IFD0: {
|
|
||||||
Copyright: 'The National Gallery'
|
|
||||||
},
|
|
||||||
IFD3: {
|
|
||||||
GPSLatitudeRef: 'N',
|
|
||||||
GPSLatitude: '51/1 30/1 3230/100',
|
|
||||||
GPSLongitudeRef: 'W',
|
|
||||||
GPSLongitude: '0/1 7/1 4366/100'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
```
|
```
|
||||||
**Example**
|
**Example**
|
||||||
@@ -249,10 +368,14 @@ const data = await sharp(input)
|
|||||||
|
|
||||||
Use these PNG options for output image.
|
Use these PNG options for output image.
|
||||||
|
|
||||||
By default, PNG output is full colour at 8 or 16 bits per pixel.
|
By default, PNG output is full colour at 8 bits per pixel.
|
||||||
|
|
||||||
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
||||||
Set `palette` to `true` for slower, indexed PNG output.
|
Set `palette` to `true` for slower, indexed PNG output.
|
||||||
|
|
||||||
|
For 16 bits per pixel output, convert to `rgb16` via
|
||||||
|
[toColourspace](/api-colour#tocolourspace).
|
||||||
|
|
||||||
|
|
||||||
**Throws**:
|
**Throws**:
|
||||||
|
|
||||||
@@ -287,6 +410,14 @@ const data = await sharp(input)
|
|||||||
.png({ palette: true })
|
.png({ palette: true })
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
```
|
```
|
||||||
|
**Example**
|
||||||
|
```js
|
||||||
|
// Output 16 bits per pixel RGB(A)
|
||||||
|
const data = await sharp(input)
|
||||||
|
.toColourspace('rgb16')
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## webp
|
## webp
|
||||||
@@ -487,6 +618,7 @@ sharp('input.svg')
|
|||||||
Use these AVIF options for output image.
|
Use these AVIF options for output image.
|
||||||
|
|
||||||
AVIF image sequences are not supported.
|
AVIF image sequences are not supported.
|
||||||
|
Prebuilt binaries support a bitdepth of 8 only.
|
||||||
|
|
||||||
|
|
||||||
**Throws**:
|
**Throws**:
|
||||||
@@ -502,6 +634,7 @@ AVIF image sequences are not supported.
|
|||||||
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
||||||
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
|
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -540,6 +673,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|||||||
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
|
||||||
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
|
||||||
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
| [options.chromaSubsampling] | <code>string</code> | <code>"'4:4:4'"</code> | set to '4:2:0' to use chroma subsampling |
|
||||||
|
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
@@ -645,7 +779,7 @@ The prebuilt binaries do not include this - see
|
|||||||
| [options.angle] | <code>number</code> | <code>0</code> | tile angle of rotation, must be a multiple of 90. |
|
| [options.angle] | <code>number</code> | <code>0</code> | tile angle of rotation, must be a multiple of 90. |
|
||||||
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"{r: 255, g: 255, b: 255, alpha: 1}"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. |
|
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"{r: 255, g: 255, b: 255, alpha: 1}"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. |
|
||||||
| [options.depth] | <code>string</code> | | how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. |
|
| [options.depth] | <code>string</code> | | how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. |
|
||||||
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images |
|
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise. |
|
||||||
| [options.container] | <code>string</code> | <code>"'fs'"</code> | tile container, with value `fs` (filesystem) or `zip` (compressed file). |
|
| [options.container] | <code>string</code> | <code>"'fs'"</code> | tile container, with value `fs` (filesystem) or `zip` (compressed file). |
|
||||||
| [options.layout] | <code>string</code> | <code>"'dz'"</code> | filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. |
|
| [options.layout] | <code>string</code> | <code>"'dz'"</code> | filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. |
|
||||||
| [options.centre] | <code>boolean</code> | <code>false</code> | centre image in tile. |
|
| [options.centre] | <code>boolean</code> | <code>false</code> | centre image in tile. |
|
||||||
|
|||||||
@@ -21,18 +21,22 @@ When using a **fit** of `cover` or `contain`, the default **position** is `centr
|
|||||||
|
|
||||||
Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
||||||
|
|
||||||
The experimental strategy-based approach resizes so one dimension is at its target length
|
The strategy-based approach initially resizes so one dimension is at its target length
|
||||||
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
||||||
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
||||||
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
||||||
|
|
||||||
Possible interpolation kernels are:
|
Possible downsizing kernels are:
|
||||||
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||||
|
- `linear`: Use a [triangle filter](https://en.wikipedia.org/wiki/Triangular_function).
|
||||||
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||||
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
- `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).
|
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
|
|
||||||
|
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
||||||
|
Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
||||||
|
|
||||||
Only one resize can occur per pipeline.
|
Only one resize can occur per pipeline.
|
||||||
Previous calls to `resize` in the same pipeline will be ignored.
|
Previous calls to `resize` in the same pipeline will be ignored.
|
||||||
|
|
||||||
@@ -52,7 +56,7 @@ Previous calls to `resize` in the same pipeline will be ignored.
|
|||||||
| [options.fit] | <code>String</code> | <code>'cover'</code> | How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`. |
|
| [options.fit] | <code>String</code> | <code>'cover'</code> | How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`. |
|
||||||
| [options.position] | <code>String</code> | <code>'centre'</code> | A position, gravity or strategy to use when `fit` is `cover` or `contain`. |
|
| [options.position] | <code>String</code> | <code>'centre'</code> | A position, gravity or strategy to use when `fit` is `cover` or `contain`. |
|
||||||
| [options.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
|
| [options.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
|
||||||
| [options.kernel] | <code>String</code> | <code>'lanczos3'</code> | The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
|
| [options.kernel] | <code>String</code> | <code>'lanczos3'</code> | The kernel to use for image reduction and the inferred interpolator to use for upsampling. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
|
||||||
| [options.withoutEnlargement] | <code>Boolean</code> | <code>false</code> | Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions. |
|
| [options.withoutEnlargement] | <code>Boolean</code> | <code>false</code> | Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions. |
|
||||||
| [options.withoutReduction] | <code>Boolean</code> | <code>false</code> | Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions. |
|
| [options.withoutReduction] | <code>Boolean</code> | <code>false</code> | Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions. |
|
||||||
| [options.fastShrinkOnLoad] | <code>Boolean</code> | <code>true</code> | Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension. |
|
| [options.fastShrinkOnLoad] | <code>Boolean</code> | <code>true</code> | Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension. |
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ const counters = sharp.counters(); // { queue: 2, process: 4 }
|
|||||||
> simd([simd]) ⇒ <code>boolean</code>
|
> simd([simd]) ⇒ <code>boolean</code>
|
||||||
|
|
||||||
Get and set use of SIMD vector unit instructions.
|
Get and set use of SIMD vector unit instructions.
|
||||||
Requires libvips to have been compiled with liborc support.
|
Requires libvips to have been compiled with highway support.
|
||||||
|
|
||||||
Improves the performance of `resize`, `blur` and `sharpen` operations
|
Improves the performance of `resize`, `blur` and `sharpen` operations
|
||||||
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
||||||
@@ -165,12 +165,12 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
|
|||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
const simd = sharp.simd();
|
const simd = sharp.simd();
|
||||||
// simd is `true` if the runtime use of liborc is currently enabled
|
// simd is `true` if the runtime use of highway is currently enabled
|
||||||
```
|
```
|
||||||
**Example**
|
**Example**
|
||||||
```js
|
```js
|
||||||
const simd = sharp.simd(false);
|
const simd = sharp.simd(false);
|
||||||
// prevent libvips from using liborc at runtime
|
// prevent libvips from using highway at runtime
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,123 @@
|
|||||||
|
|
||||||
## v0.33 - *gauge*
|
## v0.33 - *gauge*
|
||||||
|
|
||||||
Requires libvips v8.15.0
|
Requires libvips v8.15.3
|
||||||
|
|
||||||
### v0.33.0 - TBD
|
### v0.33.5 - 16th August 2024
|
||||||
|
|
||||||
* Drop support for Node.js 14 and 16, now requires Node.js >= 18.17.0
|
* Upgrade to libvips v8.15.3 for upstream bug fixes.
|
||||||
|
|
||||||
|
* Add `pageHeight` and `pages` to response of multi-page output.
|
||||||
|
[#3411](https://github.com/lovell/sharp/issues/3411)
|
||||||
|
|
||||||
|
* Ensure option to force use of a globally-installed libvips works correctly.
|
||||||
|
[#4111](https://github.com/lovell/sharp/pull/4111)
|
||||||
|
[@project0](https://github.com/project0)
|
||||||
|
|
||||||
|
* Minimise use of `engines` property to improve yarn v1 support.
|
||||||
|
[#4130](https://github.com/lovell/sharp/issues/4130)
|
||||||
|
|
||||||
|
* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries.
|
||||||
|
[#4132](https://github.com/lovell/sharp/issues/4132)
|
||||||
|
|
||||||
|
* Add support to recomb operation for 4x4 matrices.
|
||||||
|
[#4147](https://github.com/lovell/sharp/pull/4147)
|
||||||
|
[@ton11797](https://github.com/ton11797)
|
||||||
|
|
||||||
|
* Expose PNG text chunks as `comments` metadata.
|
||||||
|
[#4157](https://github.com/lovell/sharp/pull/4157)
|
||||||
|
[@nkeynes](https://github.com/nkeynes)
|
||||||
|
|
||||||
|
* Expose optional `precision` and `minAmplitude` parameters of `blur` operation.
|
||||||
|
[#4168](https://github.com/lovell/sharp/pull/4168)
|
||||||
|
[#4172](https://github.com/lovell/sharp/pull/4172)
|
||||||
|
[@marcosc90](https://github.com/marcosc90)
|
||||||
|
|
||||||
|
* Ensure `keepIccProfile` avoids colour transformation where possible.
|
||||||
|
[#4186](https://github.com/lovell/sharp/issues/4186)
|
||||||
|
|
||||||
|
* TypeScript: `chromaSubsampling` metadata is optional.
|
||||||
|
[#4191](https://github.com/lovell/sharp/pull/4191)
|
||||||
|
[@DavidVaness](https://github.com/DavidVaness)
|
||||||
|
|
||||||
|
### v0.33.4 - 16th May 2024
|
||||||
|
|
||||||
|
* Remove experimental status from `pipelineColourspace`.
|
||||||
|
|
||||||
|
* Reduce default concurrency when musl thread over-subscription detected.
|
||||||
|
|
||||||
|
* TypeScript: add missing definitions for `OverlayOptions`.
|
||||||
|
[#4048](https://github.com/lovell/sharp/pull/4048)
|
||||||
|
[@ike-gg](https://github.com/ike-gg)
|
||||||
|
|
||||||
|
* Install: add advanced option to force use of a globally-installed libvips.
|
||||||
|
[#4060](https://github.com/lovell/sharp/issues/4060)
|
||||||
|
|
||||||
|
* Expose `bilinear` resizing kernel (and interpolator).
|
||||||
|
[#4061](https://github.com/lovell/sharp/issues/4061)
|
||||||
|
|
||||||
|
* Ensure `extend` operation stays sequential for multi-page TIFF (regression in 0.32.0).
|
||||||
|
[#4069](https://github.com/lovell/sharp/issues/4069)
|
||||||
|
|
||||||
|
* Tighten validation of constructor `text` integer properties.
|
||||||
|
[#4071](https://github.com/lovell/sharp/issues/4071)
|
||||||
|
|
||||||
|
* Simplify internal StaySequential logic.
|
||||||
|
[#4074](https://github.com/lovell/sharp/pull/4074)
|
||||||
|
[@kleisauke](https://github.com/kleisauke)
|
||||||
|
|
||||||
|
* Ensure negate operation occurs after profile conversion.
|
||||||
|
[#4096](https://github.com/lovell/sharp/pull/4096)
|
||||||
|
[@adriaanmeuris](https://github.com/adriaanmeuris)
|
||||||
|
|
||||||
|
### v0.33.3 - 23rd March 2024
|
||||||
|
|
||||||
|
* Upgrade to libvips v8.15.2 for upstream bug fixes.
|
||||||
|
|
||||||
|
* Ensure `keepIccProfile` retains P3 and CMYK input profiles.
|
||||||
|
[#3906](https://github.com/lovell/sharp/issues/3906)
|
||||||
|
[#4008](https://github.com/lovell/sharp/issues/4008)
|
||||||
|
|
||||||
|
* Ensure `text.wrap` property can accept `word-char` as value.
|
||||||
|
[#4028](https://github.com/lovell/sharp/pull/4028)
|
||||||
|
[@yolopunk](https://github.com/yolopunk)
|
||||||
|
|
||||||
|
* Ensure `clone` takes a deep copy of existing options.
|
||||||
|
[#4029](https://github.com/lovell/sharp/issues/4029)
|
||||||
|
|
||||||
|
* Add `bitdepth` option to `heif` output (prebuilt binaries support 8-bit only).
|
||||||
|
[#4036](https://github.com/lovell/sharp/pull/4036)
|
||||||
|
[@mertalev](https://github.com/mertalev)
|
||||||
|
|
||||||
|
### v0.33.2 - 12th January 2024
|
||||||
|
|
||||||
|
* Upgrade to libvips v8.15.1 for upstream bug fixes.
|
||||||
|
|
||||||
|
* TypeScript: add definition for `keepMetadata`.
|
||||||
|
[#3914](https://github.com/lovell/sharp/pull/3914)
|
||||||
|
[@abhi0498](https://github.com/abhi0498)
|
||||||
|
|
||||||
|
* Ensure `extend` operation stays sequential when copying (regression in 0.32.0).
|
||||||
|
[#3928](https://github.com/lovell/sharp/issues/3928)
|
||||||
|
|
||||||
|
* Improve error handling for unsupported multi-page rotation.
|
||||||
|
[#3940](https://github.com/lovell/sharp/issues/3940)
|
||||||
|
|
||||||
|
### v0.33.1 - 17th December 2023
|
||||||
|
|
||||||
|
* Add support for Yarn Plug'n'Play filesystem layout.
|
||||||
|
[#3888](https://github.com/lovell/sharp/issues/3888)
|
||||||
|
|
||||||
|
* Emit warning when attempting to use invalid ICC profiles.
|
||||||
|
[#3895](https://github.com/lovell/sharp/issues/3895)
|
||||||
|
|
||||||
|
* Ensure `VIPS_NOVECTOR` environment variable is respected.
|
||||||
|
[#3897](https://github.com/lovell/sharp/pull/3897)
|
||||||
|
[@icetee](https://github.com/icetee)
|
||||||
|
|
||||||
|
### v0.33.0 - 29th November 2023
|
||||||
|
|
||||||
|
* Drop support for Node.js 14 and 16, now requires Node.js ^18.17.0 or >= 20.3.0
|
||||||
|
|
||||||
* Prebuilt binaries distributed via npm registry and installed via package manager.
|
* Prebuilt binaries distributed via npm registry and installed via package manager.
|
||||||
|
|
||||||
@@ -14,9 +126,18 @@ Requires libvips v8.15.0
|
|||||||
|
|
||||||
* Remove `sharp.vendor`.
|
* Remove `sharp.vendor`.
|
||||||
|
|
||||||
|
* Partially deprecate `withMetadata()`, use `withExif()` and `withIccProfile()`.
|
||||||
|
|
||||||
|
* Add experimental support for WebAssembly-based runtimes.
|
||||||
|
[@RReverser](https://github.com/RReverser)
|
||||||
|
|
||||||
* Options for `trim` operation must be an Object, add new `lineArt` option.
|
* Options for `trim` operation must be an Object, add new `lineArt` option.
|
||||||
[#2363](https://github.com/lovell/sharp/issues/2363)
|
[#2363](https://github.com/lovell/sharp/issues/2363)
|
||||||
|
|
||||||
|
* Improve luminance of `tint` operation with weighting function.
|
||||||
|
[#3338](https://github.com/lovell/sharp/issues/3338)
|
||||||
|
[@jcupitt](https://github.com/jcupitt)
|
||||||
|
|
||||||
* Ensure all `Error` objects contain a `stack` property.
|
* Ensure all `Error` objects contain a `stack` property.
|
||||||
[#3653](https://github.com/lovell/sharp/issues/3653)
|
[#3653](https://github.com/lovell/sharp/issues/3653)
|
||||||
|
|
||||||
@@ -34,6 +155,9 @@ Requires libvips v8.15.0
|
|||||||
[#3823](https://github.com/lovell/sharp/pull/3823)
|
[#3823](https://github.com/lovell/sharp/pull/3823)
|
||||||
[@uhthomas](https://github.com/uhthomas)
|
[@uhthomas](https://github.com/uhthomas)
|
||||||
|
|
||||||
|
* Add more fine-grained control over output metadata.
|
||||||
|
[#3824](https://github.com/lovell/sharp/issues/3824)
|
||||||
|
|
||||||
* Ensure multi-page extract remains sequential.
|
* Ensure multi-page extract remains sequential.
|
||||||
[#3837](https://github.com/lovell/sharp/issues/3837)
|
[#3837](https://github.com/lovell/sharp/issues/3837)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Name: Brandon Aaron
|
|||||||
GitHub: https://github.com/brandonaaron
|
GitHub: https://github.com/brandonaaron
|
||||||
|
|
||||||
Name: Andreas Lind
|
Name: Andreas Lind
|
||||||
GitHub: https://github.com/papandreouGitHub:
|
GitHub: https://github.com/papandreou
|
||||||
|
|
||||||
Name: Maurus Cuelenaere
|
Name: Maurus Cuelenaere
|
||||||
GitHub: https://github.com/mcuelenaere
|
GitHub: https://github.com/mcuelenaere
|
||||||
@@ -261,7 +261,7 @@ GitHub: https://github.com/brahima
|
|||||||
Name: Anton Marsden
|
Name: Anton Marsden
|
||||||
GitHub: https://github.com/antonmarsden
|
GitHub: https://github.com/antonmarsden
|
||||||
|
|
||||||
Name: Marcos Casagrande
|
Name: Marcos Casagrande
|
||||||
GitHub: https://github.com/marcosc90
|
GitHub: https://github.com/marcosc90
|
||||||
|
|
||||||
Name: Emanuel Jöbstl
|
Name: Emanuel Jöbstl
|
||||||
@@ -278,3 +278,27 @@ GitHub: https://github.com/bianjunjie1981
|
|||||||
|
|
||||||
Name: Dennis Beatty
|
Name: Dennis Beatty
|
||||||
GitHub: https://github.com/dnsbty
|
GitHub: https://github.com/dnsbty
|
||||||
|
|
||||||
|
Name: Ingvar Stepanyan
|
||||||
|
GitHub: https://github.com/RReverser
|
||||||
|
|
||||||
|
Name: Tamás András Horváth
|
||||||
|
GitHub: https://github.com/icetee
|
||||||
|
|
||||||
|
Name: Aaron Che
|
||||||
|
GitHub: https://github.com/yolopunk
|
||||||
|
|
||||||
|
Name: Mert Alev
|
||||||
|
GitHub: https://github.com/mertalev
|
||||||
|
|
||||||
|
Name: Adriaan Meuris
|
||||||
|
GitHub: https://github.com/adriaanmeuris
|
||||||
|
|
||||||
|
Name: Richard Hillmann
|
||||||
|
GitHub: https://github.com/project0
|
||||||
|
|
||||||
|
Name: Pongsatorn Manusopit
|
||||||
|
GitHub: https://github.com/ton11797
|
||||||
|
|
||||||
|
Name: Nathan Keynes
|
||||||
|
GitHub: https://github.com/nkeynes
|
||||||
|
|||||||
@@ -7,15 +7,13 @@
|
|||||||
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
|
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
|
||||||
<meta property="og:title" content="sharp - High performance Node.js image processing">
|
<meta property="og:title" content="sharp - High performance Node.js image processing">
|
||||||
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo-600.png">
|
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo-600.png">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; object-src 'none';
|
||||||
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
style-src 'unsafe-inline';
|
||||||
connect-src 'self' https://www.google-analytics.com;
|
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/;
|
||||||
script-src 'self' 'unsafe-inline' 'unsafe-eval'
|
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js/;">
|
||||||
https://www.google-analytics.com/analytics.js;">
|
|
||||||
<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/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-32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo-32.png">
|
||||||
<link rel="author" href="/humans.txt" type="text/plain">
|
<link rel="author" href="/humans.txt" type="text/plain">
|
||||||
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
@@ -67,7 +65,6 @@
|
|||||||
<div id="docute"></div>
|
<div id="docute"></div>
|
||||||
<script src="docute.min.js"></script>
|
<script src="docute.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
/* docute-google-analytics@1/dist/index.min.js */ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).docuteGoogleAnalytics=n()}(this,function(){"use strict";function e(e){var n;window.ga||((n=document.createElement("script")).async=!0,n.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(n),window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=Number(new Date),ga("create",e,"auto"))}function n(n,t){e(t),ga("set","page",n),ga("send","pageview")}return function(e){return{name:"@google-analytics",extend:function(t){!function(e,t){"function"==typeof e?e(function(e){n(e,t)}):e.afterEach(function(e){n(e.fullPath,t)})}(t.router,e)}}}});
|
|
||||||
const docuteApiTitlePlugin = {
|
const docuteApiTitlePlugin = {
|
||||||
name: 'apiTitle',
|
name: 'apiTitle',
|
||||||
extend(api) {
|
extend(api) {
|
||||||
@@ -126,7 +123,6 @@
|
|||||||
detectSystemDarkTheme: true,
|
detectSystemDarkTheme: true,
|
||||||
footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>',
|
footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>',
|
||||||
plugins: [
|
plugins: [
|
||||||
docuteGoogleAnalytics('UA-13034748-12'),
|
|
||||||
docuteApiTitlePlugin,
|
docuteApiTitlePlugin,
|
||||||
docuteApiSearchPlugin
|
docuteApiSearchPlugin
|
||||||
],
|
],
|
||||||
|
|||||||
139
docs/install.md
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Works with your choice of JavaScript package manager.
|
Works with your choice of JavaScript package manager.
|
||||||
|
|
||||||
|
> ⚠️ **Please ensure your package manager is configured to install optional dependencies**
|
||||||
|
|
||||||
|
If a package manager lockfile must support multiple platforms,
|
||||||
|
please see the [cross-platform](#cross-platform) section
|
||||||
|
to help decide which package manager is appropriate.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install sharp
|
npm install sharp
|
||||||
```
|
```
|
||||||
@@ -11,7 +17,7 @@ pnpm add sharp
|
|||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn add sharp # v3 recommended, Plug'n'Play unsupported
|
yarn add sharp
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -24,7 +30,7 @@ deno run --allow-ffi ...
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* Node-API v9 compatible runtime e.g. Node.js >= 18.17.0
|
* Node-API v9 compatible runtime e.g. Node.js ^18.17.0 or >=20.3.0.
|
||||||
|
|
||||||
## Prebuilt binaries
|
## Prebuilt binaries
|
||||||
|
|
||||||
@@ -34,7 +40,7 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
|
|||||||
* macOS ARM64
|
* macOS ARM64
|
||||||
* Linux ARM (glibc >= 2.28)
|
* Linux ARM (glibc >= 2.28)
|
||||||
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
|
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
|
||||||
* Linux s390x (glibc >= 2.28)
|
* Linux s390x (glibc >= 2.31)
|
||||||
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
|
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
|
||||||
* Windows x64
|
* Windows x64
|
||||||
* Windows x86
|
* Windows x86
|
||||||
@@ -42,10 +48,46 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
|
|||||||
This provides support for the
|
This provides support for the
|
||||||
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
|
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
|
||||||
|
|
||||||
|
## Cross-platform
|
||||||
|
|
||||||
|
At install time, package managers will automatically select prebuilt binaries
|
||||||
|
for the current OS platform and CPU architecture, where available.
|
||||||
|
|
||||||
|
Some package managers support multiple platforms and architectures
|
||||||
|
within the same installation tree and/or using the same lockfile.
|
||||||
|
|
||||||
|
### npm v10+
|
||||||
|
|
||||||
|
> ⚠️ **npm `package-lock.json` files can cause installation problems due to [npm bug #4828](https://github.com/npm/cli/issues/4828)**
|
||||||
|
|
||||||
|
Provides limited support via `--os`, `--cpu` and `--libc` flags.
|
||||||
|
|
||||||
|
To support macOS with Intel x64 and ARM64 CPUs:
|
||||||
|
```sh
|
||||||
|
npm install --cpu=x64 --os=darwin sharp
|
||||||
|
npm install --cpu=arm64 --os=darwin sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
When the cross-target is Linux, the C standard library must be specified.
|
||||||
|
|
||||||
|
To support glibc (e.g. Debian) and musl (e.g. Alpine) Linux with Intel x64 CPUs:
|
||||||
|
```sh
|
||||||
|
npm install --cpu=x64 --os=linux --libc=glibc sharp
|
||||||
|
npm install --cpu=x64 --os=linux --libc=musl sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
### yarn v3+
|
||||||
|
|
||||||
|
Use the [supportedArchitectures](https://yarnpkg.com/configuration/yarnrc#supportedArchitectures) configuration.
|
||||||
|
|
||||||
|
### pnpm v8+
|
||||||
|
|
||||||
|
Use the [supportedArchitectures](https://pnpm.io/package_json#pnpmsupportedarchitectures) configuration.
|
||||||
|
|
||||||
## Custom libvips
|
## Custom libvips
|
||||||
|
|
||||||
To use a custom, globally-installed version of libvips instead of the provided binaries,
|
To use a custom, globally-installed version of libvips instead of the provided binaries,
|
||||||
make sure it is at least the version listed under `engines.libvips` in the `package.json` file
|
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`.
|
and that it can be located using `pkg-config --modversion vips-cpp`.
|
||||||
|
|
||||||
For help compiling libvips and its dependencies, please see
|
For help compiling libvips and its dependencies, please see
|
||||||
@@ -58,14 +100,19 @@ and on macOS when running Node.js under Rosetta.
|
|||||||
|
|
||||||
This module will be compiled from source at `npm install` time when:
|
This module will be compiled from source at `npm install` time when:
|
||||||
|
|
||||||
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this), or
|
* a globally-installed libvips is detected, or
|
||||||
* when the `npm install --build-from-source` flag is used.
|
* when the `npm install --build-from-source` flag is used.
|
||||||
|
|
||||||
|
The logic to detect a globally-installed libvips can be skipped by setting the
|
||||||
|
`SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or
|
||||||
|
`SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated)
|
||||||
|
environment variables.
|
||||||
|
|
||||||
Building from source requires:
|
Building from source requires:
|
||||||
|
|
||||||
* C++11 compiler
|
* C++11 compiler
|
||||||
* [node-addon-api](https://www.npmjs.com/package/node-addon-api)
|
* [node-addon-api](https://www.npmjs.com/package/node-addon-api) version 7+
|
||||||
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
|
* [node-gyp](https://github.com/nodejs/node-gyp#installation) version 9+ and its dependencies
|
||||||
|
|
||||||
There is an install-time check for these dependencies.
|
There is an install-time check for these dependencies.
|
||||||
If `node-addon-api` or `node-gyp` cannot be found, try adding them via:
|
If `node-addon-api` or `node-gyp` cannot be found, try adding them via:
|
||||||
@@ -78,6 +125,19 @@ For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
|
|||||||
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
|
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
|
||||||
can be used to configure the target environment.
|
can be used to configure the target environment.
|
||||||
|
|
||||||
|
## WebAssembly
|
||||||
|
|
||||||
|
Experimental support is provided for runtime environments that provide
|
||||||
|
multi-threaded Wasm via Workers.
|
||||||
|
|
||||||
|
Use in web browsers is unsupported.
|
||||||
|
|
||||||
|
Native text rendering is unsupported.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --cpu=wasm32 sharp
|
||||||
|
```
|
||||||
|
|
||||||
## FreeBSD
|
## FreeBSD
|
||||||
|
|
||||||
The `vips` package must be installed before `npm install` is run.
|
The `vips` package must be installed before `npm install` is run.
|
||||||
@@ -115,15 +175,11 @@ must include binaries for either the linux-x64 or linux-arm64 platforms
|
|||||||
depending on the chosen architecture.
|
depending on the chosen architecture.
|
||||||
|
|
||||||
When building your deployment package on a machine that differs from the target architecture,
|
When building your deployment package on a machine that differs from the target architecture,
|
||||||
you will need to install either `@img/sharp-linux-x64` or `@img/sharp-linux-arm64` package.
|
see the [cross-platform](#cross-platform) section to help decide which package manager is appropriate
|
||||||
|
and how to configure it.
|
||||||
|
|
||||||
```sh
|
Some package managers use symbolic links
|
||||||
npm install --force @img/sharp-linux-x64
|
but AWS Lambda does not support these within deployment packages.
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install --force @img/sharp-linux-arm64
|
|
||||||
```
|
|
||||||
|
|
||||||
To get the best performance select the largest memory available.
|
To get the best performance select the largest memory available.
|
||||||
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
||||||
@@ -174,7 +230,45 @@ custom:
|
|||||||
- sharp
|
- sharp
|
||||||
packagerOptions:
|
packagerOptions:
|
||||||
scripts:
|
scripts:
|
||||||
- npm install --force @img/sharp-linux-x64
|
- npm install --os=linux --cpu=x64 sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
### electron
|
||||||
|
|
||||||
|
Ensure `sharp` is unpacked from the ASAR archive file using the
|
||||||
|
[asarUnpack](https://www.electron.build/configuration/configuration.html)
|
||||||
|
option.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"asar": true,
|
||||||
|
"asarUnpack": [
|
||||||
|
"**/node_modules/sharp/**/*",
|
||||||
|
"**/node_modules/@img/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### vite
|
||||||
|
|
||||||
|
Ensure `sharp` is excluded from bundling via the
|
||||||
|
[build.rollupOptions](https://vitejs.dev/config/build-options.html)
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
"sharp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## TypeScript
|
## TypeScript
|
||||||
@@ -213,19 +307,6 @@ use the `FONTCONFIG_PATH` environment variable to point to a custom location.
|
|||||||
|
|
||||||
Embedded SVG fonts are unsupported.
|
Embedded SVG fonts are unsupported.
|
||||||
|
|
||||||
## Worker threads
|
|
||||||
|
|
||||||
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
|
## Known conflicts
|
||||||
|
|
||||||
### Canvas and Windows
|
### Canvas and Windows
|
||||||
|
|||||||
@@ -9,26 +9,26 @@ The I/O limits of the relevant (de)compression library will generally determine
|
|||||||
|
|
||||||
## Contenders
|
## Contenders
|
||||||
|
|
||||||
* [jimp](https://www.npmjs.com/package/jimp) v0.22.7 - Image processing in pure JavaScript.
|
* [jimp](https://www.npmjs.com/package/jimp) v0.22.10 - Image processing in pure JavaScript.
|
||||||
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
* [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.25.0 - 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, but "*Project no longer maintained*".
|
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.5.3 - Image libraries transpiled to WebAssembly, includes GPLv3 code, but "*Project no longer maintained*".
|
||||||
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.3 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process, but "*Project no longer maintained*".
|
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.3 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process, but "*Project no longer maintained*".
|
||||||
* sharp v0.32.0 / libvips v8.14.2 - Caching within libvips disabled to ensure a fair comparison.
|
* sharp v0.33.0 / libvips v8.15.0 - Caching within libvips disabled to ensure a fair comparison.
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
### AMD64
|
### AMD64
|
||||||
|
|
||||||
* AWS EC2 us-east-2 [c6a.xlarge](https://aws.amazon.com/ec2/instance-types/c6a/) (4x AMD EPYC 7R13)
|
* AWS EC2 us-east-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14)
|
||||||
* Ubuntu 22.04 20230303 (ami-0122295b0eb922138)
|
* Ubuntu 23.10 [13f233a16be2](https://hub.docker.com/layers/library/ubuntu/23.10/images/sha256-13f233a16be210b57907b98b0d927ceff7571df390701e14fe1f3901b2c4a4d7)
|
||||||
* Node.js 16.19.1
|
* Node.js 20.10.0
|
||||||
|
|
||||||
### ARM64
|
### ARM64
|
||||||
|
|
||||||
* AWS EC2 us-east-2 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
|
* AWS EC2 us-east-2 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
|
||||||
* Ubuntu 22.04 20230303 (ami-0af198159897e7a29)
|
* Ubuntu 23.10 [7708743264cb](https://hub.docker.com/layers/library/ubuntu/23.10/images/sha256-7708743264cbb7f6cf7fc13e915faece45a6cdda455748bc55e58e8de3d27b63)
|
||||||
* Node.js 16.19.1
|
* Node.js 20.10.0
|
||||||
|
|
||||||
## Task: JPEG
|
## Task: JPEG
|
||||||
|
|
||||||
@@ -43,28 +43,28 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead.
|
|||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :----------------- | :----- | :----- | ------: | -------: |
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
| jimp | buffer | buffer | 0.84 | 1.0 |
|
| jimp | buffer | buffer | 0.84 | 1.0 |
|
||||||
| squoosh-cli | file | file | 1.07 | 1.3 |
|
| squoosh-cli | file | file | 1.54 | 1.8 |
|
||||||
| squoosh-lib | buffer | buffer | 1.82 | 2.2 |
|
| squoosh-lib | buffer | buffer | 2.24 | 2.7 |
|
||||||
| gm | buffer | buffer | 8.41 | 10.0 |
|
| imagemagick | file | file | 11.75 | 14.0 |
|
||||||
| gm | file | file | 8.45 | 10.0 |
|
| gm | buffer | buffer | 12.66 | 15.1 |
|
||||||
| imagemagick | file | file | 8.77 | 10.4 |
|
| gm | file | file | 12.72 | 15.1 |
|
||||||
| sharp | stream | stream | 36.36 | 43.3 |
|
| sharp | stream | stream | 48.31 | 57.5 |
|
||||||
| sharp | file | file | 38.67 | 46.0 |
|
| sharp | file | file | 51.42 | 61.2 |
|
||||||
| sharp | buffer | buffer | 39.44 | 47.0 |
|
| sharp | buffer | buffer | 52.41 | 62.4 |
|
||||||
|
|
||||||
#### Results: JPEG (ARM64)
|
#### Results: JPEG (ARM64)
|
||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :----------------- | :----- | :----- | ------: | -------: |
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
| jimp | buffer | buffer | 1.02 | 1.0 |
|
| jimp | buffer | buffer | 0.88 | 1.0 |
|
||||||
| squoosh-cli | file | file | 1.11 | 1.1 |
|
| squoosh-cli | file | file | 1.18 | 1.3 |
|
||||||
| squoosh-lib | buffer | buffer | 2.08 | 2.0 |
|
| squoosh-lib | buffer | buffer | 1.99 | 2.3 |
|
||||||
| gm | buffer | buffer | 8.80 | 8.6 |
|
| gm | buffer | buffer | 6.06 | 6.9 |
|
||||||
| gm | file | file | 10.05 | 9.9 |
|
| gm | file | file | 10.81 | 12.3 |
|
||||||
| imagemagick | file | file | 10.28 | 10.1 |
|
| imagemagick | file | file | 10.95 | 12.4 |
|
||||||
| sharp | stream | stream | 26.87 | 26.3 |
|
| sharp | stream | stream | 33.15 | 37.7 |
|
||||||
| sharp | file | file | 27.88 | 27.3 |
|
| sharp | file | file | 34.99 | 39.8 |
|
||||||
| sharp | buffer | buffer | 28.40 | 27.8 |
|
| sharp | buffer | buffer | 36.05 | 41.0 |
|
||||||
|
|
||||||
## Task: PNG
|
## Task: PNG
|
||||||
|
|
||||||
@@ -80,25 +80,25 @@ Note: jimp does not support premultiply/unpremultiply.
|
|||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :----------------- | :----- | :----- | ------: | -------: |
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
| squoosh-cli | file | file | 0.40 | 1.0 |
|
| squoosh-cli | file | file | 0.34 | 1.0 |
|
||||||
| squoosh-lib | buffer | buffer | 0.47 | 1.2 |
|
| squoosh-lib | buffer | buffer | 0.51 | 1.5 |
|
||||||
| gm | file | file | 6.47 | 16.2 |
|
| jimp | buffer | buffer | 3.59 | 10.6 |
|
||||||
| jimp | buffer | buffer | 6.60 | 16.5 |
|
| gm | file | file | 8.54 | 25.1 |
|
||||||
| imagemagick | file | file | 7.08 | 17.7 |
|
| imagemagick | file | file | 9.23 | 27.1 |
|
||||||
| sharp | file | file | 17.80 | 44.5 |
|
| sharp | file | file | 25.43 | 74.8 |
|
||||||
| sharp | buffer | buffer | 18.02 | 45.0 |
|
| sharp | buffer | buffer | 25.70 | 75.6 |
|
||||||
|
|
||||||
### Results: PNG (ARM64)
|
### Results: PNG (ARM64)
|
||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :----------------- | :----- | :----- | ------: | -------: |
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
| squoosh-cli | file | file | 0.40 | 1.0 |
|
| squoosh-cli | file | file | 0.33 | 1.0 |
|
||||||
| squoosh-lib | buffer | buffer | 0.48 | 1.2 |
|
| squoosh-lib | buffer | buffer | 0.46 | 1.4 |
|
||||||
| gm | file | file | 7.20 | 18.0 |
|
| jimp | buffer | buffer | 3.51 | 10.6 |
|
||||||
| jimp | buffer | buffer | 7.62 | 19.1 |
|
| gm | file | file | 7.47 | 22.6 |
|
||||||
| imagemagick | file | file | 7.96 | 19.9 |
|
| imagemagick | file | file | 8.06 | 24.4 |
|
||||||
| sharp | file | file | 12.97 | 32.4 |
|
| sharp | file | file | 17.31 | 52.5 |
|
||||||
| sharp | buffer | buffer | 13.12 | 32.8 |
|
| sharp | buffer | buffer | 17.66 | 53.5 |
|
||||||
|
|
||||||
## Running the benchmark test
|
## Running the benchmark test
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ module.exports = [
|
|||||||
'current',
|
'current',
|
||||||
'date',
|
'date',
|
||||||
'default',
|
'default',
|
||||||
|
'deprecated',
|
||||||
'does',
|
'does',
|
||||||
'each',
|
'each',
|
||||||
'either',
|
'either',
|
||||||
@@ -46,6 +47,7 @@ module.exports = [
|
|||||||
'given',
|
'given',
|
||||||
'has',
|
'has',
|
||||||
'have',
|
'have',
|
||||||
|
'helps',
|
||||||
'how',
|
'how',
|
||||||
'image',
|
'image',
|
||||||
'implies',
|
'implies',
|
||||||
@@ -58,6 +60,7 @@ module.exports = [
|
|||||||
'lots',
|
'lots',
|
||||||
'make',
|
'make',
|
||||||
'may',
|
'may',
|
||||||
|
'meaning',
|
||||||
'more',
|
'more',
|
||||||
'most',
|
'most',
|
||||||
'much',
|
'much',
|
||||||
@@ -79,6 +82,7 @@ module.exports = [
|
|||||||
'pre',
|
'pre',
|
||||||
'previously',
|
'previously',
|
||||||
'produce',
|
'produce',
|
||||||
|
'proper',
|
||||||
'provide',
|
'provide',
|
||||||
'provided',
|
'provided',
|
||||||
'ready',
|
'ready',
|
||||||
@@ -116,6 +120,7 @@ module.exports = [
|
|||||||
'unless',
|
'unless',
|
||||||
'unmaintained',
|
'unmaintained',
|
||||||
'unsuitable',
|
'unsuitable',
|
||||||
|
'unsupported',
|
||||||
'until',
|
'until',
|
||||||
'use',
|
'use',
|
||||||
'used',
|
'used',
|
||||||
|
|||||||
@@ -3,34 +3,39 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips');
|
try {
|
||||||
|
const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips');
|
||||||
|
|
||||||
const buildFromSource = (msg) => {
|
const buildFromSource = (msg) => {
|
||||||
log(msg);
|
log(msg);
|
||||||
log('Attempting to build from source via node-gyp');
|
log('Attempting to build from source via node-gyp');
|
||||||
try {
|
try {
|
||||||
require('node-addon-api');
|
const addonApi = require('node-addon-api');
|
||||||
log('Found node-addon-api');
|
log(`Found node-addon-api ${addonApi.version || ''}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log('Please add node-addon-api to your dependencies');
|
log('Please add node-addon-api to your dependencies');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const gyp = require('node-gyp');
|
const gyp = require('node-gyp');
|
||||||
log(`Found node-gyp version ${gyp().version}`);
|
log(`Found node-gyp ${gyp().version}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log('Please add node-gyp to your dependencies');
|
log('Please add node-gyp to your dependencies');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log('See https://sharp.pixelplumbing.com/install#building-from-source');
|
log('See https://sharp.pixelplumbing.com/install#building-from-source');
|
||||||
const status = spawnRebuild();
|
const status = spawnRebuild();
|
||||||
if (status !== 0) {
|
if (status !== 0) {
|
||||||
process.exit(status);
|
process.exit(status);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (useGlobalLibvips()) {
|
if (useGlobalLibvips(log)) {
|
||||||
buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
|
buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
|
||||||
} else if (process.env.npm_config_build_from_source) {
|
} else if (process.env.npm_config_build_from_source) {
|
||||||
buildFromSource('Detected --build-from-source flag');
|
buildFromSource('Detected --build-from-source flag');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const summary = err.message.split(/\n/).slice(0, 1);
|
||||||
|
console.log(`sharp: skipping install check: ${summary}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const colourspace = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tint the image using the provided chroma while preserving the image luminance.
|
* Tint the image using the provided colour.
|
||||||
* An alpha channel may be present and will be unchanged by the operation.
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -27,14 +27,12 @@ const colourspace = {
|
|||||||
* .tint({ r: 255, g: 240, b: 16 })
|
* .tint({ r: 255, g: 240, b: 16 })
|
||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
* @param {string|Object} tint - Parsed by the [color](https://www.npmjs.org/package/color) module.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameter
|
* @throws {Error} Invalid parameter
|
||||||
*/
|
*/
|
||||||
function tint (rgb) {
|
function tint (tint) {
|
||||||
const colour = color(rgb);
|
this._setBackgroundColourOption('tint', tint);
|
||||||
this.options.tintA = colour.a();
|
|
||||||
this.options.tintB = colour.b();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +71,6 @@ function grayscale (grayscale) {
|
|||||||
* All operations will use this colourspace before converting to the output colourspace,
|
* All operations will use this colourspace before converting to the output colourspace,
|
||||||
* as defined by {@link #tocolourspace|toColourspace}.
|
* as defined by {@link #tocolourspace|toColourspace}.
|
||||||
*
|
*
|
||||||
* This feature is experimental and has not yet been fully-tested with all operations.
|
|
||||||
*
|
|
||||||
* @since 0.29.0
|
* @since 0.29.0
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -92,7 +88,7 @@ function pipelineColourspace (colourspace) {
|
|||||||
if (!is.string(colourspace)) {
|
if (!is.string(colourspace)) {
|
||||||
throw is.invalidParameterError('colourspace', 'string', colourspace);
|
throw is.invalidParameterError('colourspace', 'string', colourspace);
|
||||||
}
|
}
|
||||||
this.options.colourspaceInput = colourspace;
|
this.options.colourspacePipeline = colourspace;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ const debuglog = util.debuglog('sharp');
|
|||||||
*
|
*
|
||||||
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
*
|
*
|
||||||
|
* When loading more than one page/frame of an animated image,
|
||||||
|
* these are combined as a vertically-stacked "toilet roll" image
|
||||||
|
* where the overall height is the `pageHeight` multiplied by the number of `pages`.
|
||||||
|
*
|
||||||
* @constructs Sharp
|
* @constructs Sharp
|
||||||
*
|
*
|
||||||
* @emits Sharp#info
|
* @emits Sharp#info
|
||||||
@@ -36,14 +40,16 @@ const debuglog = util.debuglog('sharp');
|
|||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // Read image data from readableStream,
|
* // Read image data from remote URL,
|
||||||
* // resize to 300 pixels wide,
|
* // resize to 300 pixels wide,
|
||||||
* // emit an 'info' event with calculated dimensions
|
* // emit an 'info' event with calculated dimensions
|
||||||
* // and finally write image data to writableStream
|
* // and finally write image data to writableStream
|
||||||
* var transformer = sharp()
|
* const { body } = fetch('https://...');
|
||||||
|
* const readableStream = Readable.fromWeb(body);
|
||||||
|
* const transformer = sharp()
|
||||||
* .resize(300)
|
* .resize(300)
|
||||||
* .on('info', function(info) {
|
* .on('info', ({ height }) => {
|
||||||
* console.log('Image height is ' + info.height);
|
* console.log(`Image height is ${height}`);
|
||||||
* });
|
* });
|
||||||
* readableStream.pipe(transformer).pipe(writableStream);
|
* readableStream.pipe(transformer).pipe(writableStream);
|
||||||
*
|
*
|
||||||
@@ -160,7 +166,7 @@ const debuglog = util.debuglog('sharp');
|
|||||||
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
* @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 {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.
|
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
||||||
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'.
|
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
@@ -212,8 +218,7 @@ const Sharp = function (input, options) {
|
|||||||
kernel: 'lanczos3',
|
kernel: 'lanczos3',
|
||||||
fastShrinkOnLoad: true,
|
fastShrinkOnLoad: true,
|
||||||
// operations
|
// operations
|
||||||
tintA: 128,
|
tint: [-1, 0, 0, 0],
|
||||||
tintB: 128,
|
|
||||||
flatten: false,
|
flatten: false,
|
||||||
flattenBackground: [0, 0, 0],
|
flattenBackground: [0, 0, 0],
|
||||||
unflatten: false,
|
unflatten: false,
|
||||||
@@ -221,6 +226,8 @@ const Sharp = function (input, options) {
|
|||||||
negateAlpha: true,
|
negateAlpha: true,
|
||||||
medianSize: 0,
|
medianSize: 0,
|
||||||
blurSigma: 0,
|
blurSigma: 0,
|
||||||
|
precision: 'integer',
|
||||||
|
minAmpl: 0.2,
|
||||||
sharpenSigma: 0,
|
sharpenSigma: 0,
|
||||||
sharpenM1: 1,
|
sharpenM1: 1,
|
||||||
sharpenM2: 2,
|
sharpenM2: 2,
|
||||||
@@ -252,17 +259,18 @@ const Sharp = function (input, options) {
|
|||||||
removeAlpha: false,
|
removeAlpha: false,
|
||||||
ensureAlpha: -1,
|
ensureAlpha: -1,
|
||||||
colourspace: 'srgb',
|
colourspace: 'srgb',
|
||||||
colourspaceInput: 'last',
|
colourspacePipeline: 'last',
|
||||||
composite: [],
|
composite: [],
|
||||||
// output
|
// output
|
||||||
fileOut: '',
|
fileOut: '',
|
||||||
formatOut: 'input',
|
formatOut: 'input',
|
||||||
streamOut: false,
|
streamOut: false,
|
||||||
withMetadata: false,
|
keepMetadata: 0,
|
||||||
withMetadataOrientation: -1,
|
withMetadataOrientation: -1,
|
||||||
withMetadataDensity: 0,
|
withMetadataDensity: 0,
|
||||||
withMetadataIcc: '',
|
withIccProfile: '',
|
||||||
withMetadataStrs: {},
|
withExif: {},
|
||||||
|
withExifMerge: true,
|
||||||
resolveWithObject: false,
|
resolveWithObject: false,
|
||||||
// output format
|
// output format
|
||||||
jpegQuality: 80,
|
jpegQuality: 80,
|
||||||
@@ -319,6 +327,7 @@ const Sharp = function (input, options) {
|
|||||||
heifCompression: 'av1',
|
heifCompression: 'av1',
|
||||||
heifEffort: 4,
|
heifEffort: 4,
|
||||||
heifChromaSubsampling: '4:4:4',
|
heifChromaSubsampling: '4:4:4',
|
||||||
|
heifBitdepth: 8,
|
||||||
jxlDistance: 1,
|
jxlDistance: 1,
|
||||||
jxlDecodingTier: 0,
|
jxlDecodingTier: 0,
|
||||||
jxlEffort: 7,
|
jxlEffort: 7,
|
||||||
@@ -419,13 +428,16 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
|
|||||||
function clone () {
|
function clone () {
|
||||||
// Clone existing options
|
// Clone existing options
|
||||||
const clone = this.constructor.call();
|
const clone = this.constructor.call();
|
||||||
clone.options = Object.assign({}, this.options);
|
const { debuglog, queueListener, ...options } = this.options;
|
||||||
|
clone.options = structuredClone(options);
|
||||||
|
clone.options.debuglog = debuglog;
|
||||||
|
clone.options.queueListener = queueListener;
|
||||||
// Pass 'finish' event to clone for Stream-based input
|
// Pass 'finish' event to clone for Stream-based input
|
||||||
if (this._isStreamInput()) {
|
if (this._isStreamInput()) {
|
||||||
this.on('finish', () => {
|
this.on('finish', () => {
|
||||||
// Clone inherits input data
|
// Clone inherits input data
|
||||||
this._flattenBufferIn();
|
this._flattenBufferIn();
|
||||||
clone.options.bufferIn = this.options.bufferIn;
|
clone.options.input.buffer = this.options.input.buffer;
|
||||||
clone.emit('finish');
|
clone.emit('finish');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
130
lib/index.d.ts
vendored
@@ -126,7 +126,7 @@ declare namespace sharp {
|
|||||||
function counters(): SharpCounters;
|
function counters(): SharpCounters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with liborc support.
|
* Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with highway support.
|
||||||
* Improves the performance of resize, blur and sharpen operations by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
* Improves the performance of resize, blur and sharpen operations by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
||||||
* @param enable enable or disable use of SIMD vector unit instructions
|
* @param enable enable or disable use of SIMD vector unit instructions
|
||||||
* @returns true if usage of SIMD vector unit instructions is enabled
|
* @returns true if usage of SIMD vector unit instructions is enabled
|
||||||
@@ -239,12 +239,12 @@ declare namespace sharp {
|
|||||||
//#region Color functions
|
//#region Color functions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tint the image using the provided chroma while preserving the image luminance.
|
* Tint the image using the provided colour.
|
||||||
* An alpha channel may be present and will be unchanged by the operation.
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
* @param rgb Parsed by the color module to extract chroma values.
|
* @param tint Parsed by the color module.
|
||||||
* @returns A sharp instance that can be used to chain operations
|
* @returns A sharp instance that can be used to chain operations
|
||||||
*/
|
*/
|
||||||
tint(rgb: Color): Sharp;
|
tint(tint: Color): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert to 8-bit greyscale; 256 shades of grey.
|
* Convert to 8-bit greyscale; 256 shades of grey.
|
||||||
@@ -341,6 +341,12 @@ declare namespace sharp {
|
|||||||
*/
|
*/
|
||||||
metadata(): Promise<Metadata>;
|
metadata(): Promise<Metadata>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
*/
|
||||||
|
keepMetadata(): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access to pixel-derived image statistics for every channel in the image.
|
* Access to pixel-derived image statistics for every channel in the image.
|
||||||
* @returns A sharp instance that can be used to chain operations
|
* @returns A sharp instance that can be used to chain operations
|
||||||
@@ -458,7 +464,7 @@ declare namespace sharp {
|
|||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
* @returns A sharp instance that can be used to chain operations
|
* @returns A sharp instance that can be used to chain operations
|
||||||
*/
|
*/
|
||||||
blur(sigma?: number | boolean): Sharp;
|
blur(sigma?: number | boolean | BlurOptions): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge alpha transparency channel, if any, with background.
|
* Merge alpha transparency channel, if any, with background.
|
||||||
@@ -565,11 +571,11 @@ declare namespace sharp {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Recomb the image with the specified matrix.
|
* Recomb the image with the specified matrix.
|
||||||
* @param inputMatrix 3x3 Recombination matrix
|
* @param inputMatrix 3x3 Recombination matrix or 4x4 Recombination matrix
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
* @returns A sharp instance that can be used to chain operations
|
* @returns A sharp instance that can be used to chain operations
|
||||||
*/
|
*/
|
||||||
recomb(inputMatrix: Matrix3x3): Sharp;
|
recomb(inputMatrix: Matrix3x3 | Matrix4x4): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the image using brightness, saturation, hue rotation and lightness.
|
* Transforms the image using brightness, saturation, hue rotation and lightness.
|
||||||
@@ -633,6 +639,43 @@ declare namespace sharp {
|
|||||||
*/
|
*/
|
||||||
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
|
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep all EXIF metadata from the input image in the output image.
|
||||||
|
* EXIF metadata is unsupported for TIFF output.
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
*/
|
||||||
|
keepExif(): Sharp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||||
|
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
withExif(exif: Exif): Sharp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update EXIF metadata from the input image in the output image.
|
||||||
|
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
withExifMerge(exif: Exif): Sharp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep ICC profile from the input image in the output image where possible.
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
*/
|
||||||
|
keepIccProfile(): Sharp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform using an ICC profile and attach to the output image.
|
||||||
|
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||||
|
* @returns A sharp instance that can be used to chain operations
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||||
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||||
@@ -640,7 +683,7 @@ declare namespace sharp {
|
|||||||
* @param withMetadata
|
* @param withMetadata
|
||||||
* @throws {Error} Invalid parameters.
|
* @throws {Error} Invalid parameters.
|
||||||
*/
|
*/
|
||||||
withMetadata(withMetadata?: boolean | WriteableMetadata): Sharp;
|
withMetadata(withMetadata?: WriteableMetadata): Sharp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use these JPEG options for output image.
|
* Use these JPEG options for output image.
|
||||||
@@ -974,19 +1017,36 @@ declare namespace sharp {
|
|||||||
rgba?: boolean;
|
rgba?: boolean;
|
||||||
/** Text line height in points. Will use the font line height if none is specified. (optional, default `0`) */
|
/** Text line height in points. Will use the font line height if none is specified. (optional, default `0`) */
|
||||||
spacing?: number;
|
spacing?: number;
|
||||||
/** Word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none' */
|
/** Word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none' */
|
||||||
wrap?: TextWrap;
|
wrap?: TextWrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExifDir {
|
||||||
|
[k: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Exif {
|
||||||
|
'IFD0'?: ExifDir;
|
||||||
|
'IFD1'?: ExifDir;
|
||||||
|
'IFD2'?: ExifDir;
|
||||||
|
'IFD3'?: ExifDir;
|
||||||
|
}
|
||||||
|
|
||||||
interface WriteableMetadata {
|
interface WriteableMetadata {
|
||||||
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
|
||||||
orientation?: number | undefined;
|
|
||||||
/** Filesystem path to output ICC profile, defaults to sRGB. */
|
|
||||||
icc?: string | undefined;
|
|
||||||
/** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default {}) */
|
|
||||||
exif?: Record<string, any> | undefined;
|
|
||||||
/** Number of pixels per inch (DPI) */
|
/** Number of pixels per inch (DPI) */
|
||||||
density?: number | undefined;
|
density?: number | undefined;
|
||||||
|
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
||||||
|
orientation?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Filesystem path to output ICC profile, defaults to sRGB.
|
||||||
|
* @deprecated Use `withIccProfile()` instead.
|
||||||
|
*/
|
||||||
|
icc?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||||
|
* @deprecated Use `withExif()` or `withExifMerge()` instead.
|
||||||
|
*/
|
||||||
|
exif?: Exif | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Metadata {
|
interface Metadata {
|
||||||
@@ -1009,7 +1069,7 @@ declare namespace sharp {
|
|||||||
/** Number of pixels per inch (DPI), if present */
|
/** Number of pixels per inch (DPI), if present */
|
||||||
density?: number | undefined;
|
density?: number | undefined;
|
||||||
/** 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 */
|
/** 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 */
|
||||||
chromaSubsampling: string;
|
chromaSubsampling?: string | undefined;
|
||||||
/** Boolean indicating whether the image is interlaced using a progressive scan */
|
/** Boolean indicating whether the image is interlaced using a progressive scan */
|
||||||
isProgressive?: boolean | undefined;
|
isProgressive?: boolean | undefined;
|
||||||
/** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */
|
/** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */
|
||||||
@@ -1048,6 +1108,8 @@ declare namespace sharp {
|
|||||||
resolutionUnit?: 'inch' | 'cm' | undefined;
|
resolutionUnit?: 'inch' | 'cm' | undefined;
|
||||||
/** String containing format for images loaded via *magick */
|
/** String containing format for images loaded via *magick */
|
||||||
formatMagick?: string | undefined;
|
formatMagick?: string | undefined;
|
||||||
|
/** Array of keyword/text pairs representing PNG text blocks, if present. */
|
||||||
|
comments?: CommentsMetadata[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LevelMetadata {
|
interface LevelMetadata {
|
||||||
@@ -1055,6 +1117,11 @@ declare namespace sharp {
|
|||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommentsMetadata {
|
||||||
|
keyword: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Stats {
|
interface Stats {
|
||||||
/** Array of channel statistics for each channel in the image. */
|
/** Array of channel statistics for each channel in the image. */
|
||||||
channels: ChannelStats[];
|
channels: ChannelStats[];
|
||||||
@@ -1096,6 +1163,11 @@ declare namespace sharp {
|
|||||||
force?: boolean | undefined;
|
force?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WithIccProfileOptions {
|
||||||
|
/** Should the ICC profile be included in the output image metadata? (optional, default true) */
|
||||||
|
attach?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
interface JpegOptions extends OutputOptions {
|
interface JpegOptions extends OutputOptions {
|
||||||
/** Quality, integer 1-100 (optional, default 80) */
|
/** Quality, integer 1-100 (optional, default 80) */
|
||||||
quality?: number | undefined;
|
quality?: number | undefined;
|
||||||
@@ -1179,6 +1251,8 @@ declare namespace sharp {
|
|||||||
effort?: number | undefined;
|
effort?: number | undefined;
|
||||||
/** set to '4:2:0' to use chroma subsampling, requires libvips v8.11.0 (optional, default '4:4:4') */
|
/** set to '4:2:0' to use chroma subsampling, requires libvips v8.11.0 (optional, default '4:4:4') */
|
||||||
chromaSubsampling?: string | undefined;
|
chromaSubsampling?: string | undefined;
|
||||||
|
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
||||||
|
bitdepth?: 8 | 10 | 12 | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeifOptions extends OutputOptions {
|
interface HeifOptions extends OutputOptions {
|
||||||
@@ -1192,6 +1266,8 @@ declare namespace sharp {
|
|||||||
effort?: number | undefined;
|
effort?: number | undefined;
|
||||||
/** set to '4:2:0' to use chroma subsampling (optional, default '4:4:4') */
|
/** set to '4:2:0' to use chroma subsampling (optional, default '4:4:4') */
|
||||||
chromaSubsampling?: string | undefined;
|
chromaSubsampling?: string | undefined;
|
||||||
|
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
|
||||||
|
bitdepth?: 8 | 10 | 12 | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GifOptions extends OutputOptions, AnimationOptions {
|
interface GifOptions extends OutputOptions, AnimationOptions {
|
||||||
@@ -1266,6 +1342,17 @@ declare namespace sharp {
|
|||||||
background?: Color | undefined;
|
background?: Color | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Precision = 'integer' | 'float' | 'approximate';
|
||||||
|
|
||||||
|
interface BlurOptions {
|
||||||
|
/** A value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2` */
|
||||||
|
sigma: number;
|
||||||
|
/** A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask. */
|
||||||
|
minAmplitude?: number;
|
||||||
|
/** How accurate the operation should be, one of: integer, float, approximate. (optional, default "integer") */
|
||||||
|
precision?: Precision | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
interface FlattenOptions {
|
interface FlattenOptions {
|
||||||
/** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */
|
/** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */
|
||||||
background?: Color | undefined;
|
background?: Color | undefined;
|
||||||
@@ -1410,6 +1497,14 @@ declare namespace sharp {
|
|||||||
tile?: boolean | undefined;
|
tile?: boolean | undefined;
|
||||||
/** Set to true to avoid premultipling the image below. Equivalent to the --premultiplied vips option. */
|
/** Set to true to avoid premultipling the image below. Equivalent to the --premultiplied vips option. */
|
||||||
premultiplied?: boolean | undefined;
|
premultiplied?: boolean | undefined;
|
||||||
|
/** number representing the DPI for vector overlay image. (optional, default 72)*/
|
||||||
|
density?: number | undefined;
|
||||||
|
/** Set to true to read all frames/pages of an animated image. (optional, default false) */
|
||||||
|
animated?: boolean | undefined;
|
||||||
|
/** see sharp() constructor, (optional, default 'warning') */
|
||||||
|
failOn?: FailOnOptions | undefined;
|
||||||
|
/** see sharp() constructor, (optional, default 268402689) */
|
||||||
|
limitInputPixels?: number | boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TileOptions {
|
interface TileOptions {
|
||||||
@@ -1548,7 +1643,7 @@ declare namespace sharp {
|
|||||||
|
|
||||||
type TextAlign = 'left' | 'centre' | 'center' | 'right';
|
type TextAlign = 'left' | 'centre' | 'center' | 'right';
|
||||||
|
|
||||||
type TextWrap = 'word' | 'char' | 'charWord' | 'none';
|
type TextWrap = 'word' | 'char' | 'word-char' | 'none';
|
||||||
|
|
||||||
type TileContainer = 'fs' | 'zip';
|
type TileContainer = 'fs' | 'zip';
|
||||||
|
|
||||||
@@ -1653,6 +1748,7 @@ declare namespace sharp {
|
|||||||
|
|
||||||
type Matrix2x2 = [[number, number], [number, number]];
|
type Matrix2x2 = [[number, number], [number, number]];
|
||||||
type Matrix3x3 = [[number, number, number], [number, number, number], [number, number, number]];
|
type Matrix3x3 = [[number, number, number], [number, number, number], [number, number, number]];
|
||||||
|
type Matrix4x4 = [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]];
|
||||||
}
|
}
|
||||||
|
|
||||||
export = sharp;
|
export = sharp;
|
||||||
|
|||||||
21
lib/input.js
@@ -296,17 +296,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.width)) {
|
if (is.defined(inputOptions.text.width)) {
|
||||||
if (is.number(inputOptions.text.width)) {
|
if (is.integer(inputOptions.text.width) && inputOptions.text.width > 0) {
|
||||||
inputDescriptor.textWidth = inputOptions.text.width;
|
inputDescriptor.textWidth = inputOptions.text.width;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
|
throw is.invalidParameterError('text.width', 'positive integer', inputOptions.text.width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.height)) {
|
if (is.defined(inputOptions.text.height)) {
|
||||||
if (is.number(inputOptions.text.height)) {
|
if (is.integer(inputOptions.text.height) && inputOptions.text.height > 0) {
|
||||||
inputDescriptor.textHeight = inputOptions.text.height;
|
inputDescriptor.textHeight = inputOptions.text.height;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
|
throw is.invalidParameterError('text.height', 'positive integer', inputOptions.text.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.align)) {
|
if (is.defined(inputOptions.text.align)) {
|
||||||
@@ -324,10 +324,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.dpi)) {
|
if (is.defined(inputOptions.text.dpi)) {
|
||||||
if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
|
if (is.integer(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 1000000)) {
|
||||||
inputDescriptor.textDpi = inputOptions.text.dpi;
|
inputDescriptor.textDpi = inputOptions.text.dpi;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
|
throw is.invalidParameterError('text.dpi', 'integer between 1 and 1000000', inputOptions.text.dpi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.rgba)) {
|
if (is.defined(inputOptions.text.rgba)) {
|
||||||
@@ -338,17 +338,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.spacing)) {
|
if (is.defined(inputOptions.text.spacing)) {
|
||||||
if (is.number(inputOptions.text.spacing)) {
|
if (is.integer(inputOptions.text.spacing) && is.inRange(inputOptions.text.spacing, -1000000, 1000000)) {
|
||||||
inputDescriptor.textSpacing = inputOptions.text.spacing;
|
inputDescriptor.textSpacing = inputOptions.text.spacing;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
|
throw is.invalidParameterError('text.spacing', 'integer between -1000000 and 1000000', inputOptions.text.spacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.text.wrap)) {
|
if (is.defined(inputOptions.text.wrap)) {
|
||||||
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
|
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'word-char', 'none'])) {
|
||||||
inputDescriptor.textWrap = inputOptions.text.wrap;
|
inputDescriptor.textWrap = inputOptions.text.wrap;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
|
throw is.invalidParameterError('text.wrap', 'one of: word, char, word-char, none', inputOptions.text.wrap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete inputDescriptor.buffer;
|
delete inputDescriptor.buffer;
|
||||||
@@ -450,6 +450,7 @@ function _isStreamInput () {
|
|||||||
* - `xmp`: Buffer containing raw XMP 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
|
||||||
* - `formatMagick`: String containing format for images loaded via *magick
|
* - `formatMagick`: String containing format for images loaded via *magick
|
||||||
|
* - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const metadata = await sharp(input).metadata();
|
* const metadata = await sharp(input).metadata();
|
||||||
|
|||||||
@@ -4,19 +4,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { spawnSync } = require('node:child_process');
|
const { spawnSync } = require('node:child_process');
|
||||||
|
const { createHash } = require('node:crypto');
|
||||||
const semverCoerce = require('semver/functions/coerce');
|
const semverCoerce = require('semver/functions/coerce');
|
||||||
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
||||||
|
const semverSatisfies = require('semver/functions/satisfies');
|
||||||
const detectLibc = require('detect-libc');
|
const detectLibc = require('detect-libc');
|
||||||
|
|
||||||
const { engines } = require('../package.json');
|
const { config, engines, optionalDependencies } = require('../package.json');
|
||||||
|
|
||||||
const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */
|
const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */
|
||||||
engines.libvips;
|
config.libvips;
|
||||||
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
||||||
|
|
||||||
const prebuiltPlatforms = [
|
const prebuiltPlatforms = [
|
||||||
'darwin-arm64', 'darwin-x64',
|
'darwin-arm64', 'darwin-x64',
|
||||||
'linux-arm', 'linux-arm64', 'linux-x64',
|
'linux-arm', 'linux-arm64', 'linux-s390x', 'linux-x64',
|
||||||
'linuxmusl-arm64', 'linuxmusl-x64',
|
'linuxmusl-arm64', 'linuxmusl-x64',
|
||||||
'win32-ia32', 'win32-x64'
|
'win32-ia32', 'win32-x64'
|
||||||
];
|
];
|
||||||
@@ -41,15 +43,23 @@ const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process
|
|||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
const buildPlatformArch = () => {
|
const buildPlatformArch = () => {
|
||||||
|
if (isEmscripten()) {
|
||||||
|
return 'wasm32';
|
||||||
|
}
|
||||||
/* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */
|
/* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */
|
||||||
const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env;
|
const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env;
|
||||||
return `${npm_config_platform || process.platform}${npm_config_libc || runtimeLibc()}-${npm_config_arch || process.arch}`;
|
const libc = typeof npm_config_libc === 'string' ? npm_config_libc : runtimeLibc();
|
||||||
|
return `${npm_config_platform || process.platform}${libc}-${npm_config_arch || process.arch}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildSharpLibvipsIncludeDir = () => {
|
const buildSharpLibvipsIncludeDir = () => {
|
||||||
try {
|
try {
|
||||||
return require('@img/sharp-libvips-dev/include');
|
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/include`);
|
||||||
} catch {}
|
} catch {
|
||||||
|
try {
|
||||||
|
return require('@img/sharp-libvips-dev/include');
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
@@ -64,12 +74,31 @@ const buildSharpLibvipsCPlusPlusDir = () => {
|
|||||||
|
|
||||||
const buildSharpLibvipsLibDir = () => {
|
const buildSharpLibvipsLibDir = () => {
|
||||||
try {
|
try {
|
||||||
return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
|
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/lib`);
|
||||||
} catch {}
|
} catch {
|
||||||
|
try {
|
||||||
|
return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isUnsupportedNodeRuntime = () => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (process.release?.name === 'node' && process.versions) {
|
||||||
|
if (!semverSatisfies(process.versions.node, engines.node)) {
|
||||||
|
return { found: process.versions.node, expected: engines.node };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const isEmscripten = () => {
|
||||||
|
const { CC } = process.env;
|
||||||
|
return Boolean(CC && CC.endsWith('/emcc'));
|
||||||
|
};
|
||||||
|
|
||||||
const isRosetta = () => {
|
const isRosetta = () => {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (process.platform === 'darwin' && process.arch === 'x64') {
|
if (process.platform === 'darwin' && process.arch === 'x64') {
|
||||||
@@ -79,9 +108,20 @@ const isRosetta = () => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sha512 = (s) => createHash('sha512').update(s).digest('hex');
|
||||||
|
|
||||||
|
const yarnLocator = () => {
|
||||||
|
try {
|
||||||
|
const identHash = sha512(`imgsharp-libvips-${buildPlatformArch()}`);
|
||||||
|
const npmVersion = semverCoerce(optionalDependencies[`@img/sharp-libvips-${buildPlatformArch()}`]).version;
|
||||||
|
return sha512(`${identHash}npm:${npmVersion}`).slice(0, 10);
|
||||||
|
} catch {}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
const spawnRebuild = () =>
|
const spawnRebuild = () =>
|
||||||
spawnSync('node-gyp rebuild --directory=src', {
|
spawnSync(`node-gyp rebuild --directory=src ${isEmscripten() ? '--nodedir=emscripten' : ''}`, {
|
||||||
...spawnSyncOptions,
|
...spawnSyncOptions,
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
}).status;
|
}).status;
|
||||||
@@ -122,15 +162,23 @@ const pkgConfigPath = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const useGlobalLibvips = () => {
|
const skipSearch = (status, reason, logger) => {
|
||||||
|
if (logger) {
|
||||||
|
logger(`Detected ${reason}, skipping search for globally-installed libvips`);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useGlobalLibvips = (logger) => {
|
||||||
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
|
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
|
||||||
log('Detected SHARP_IGNORE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
|
return skipSearch(false, 'SHARP_IGNORE_GLOBAL_LIBVIPS', logger);
|
||||||
return false;
|
}
|
||||||
|
if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) {
|
||||||
|
return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS', logger);
|
||||||
}
|
}
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (isRosetta()) {
|
if (isRosetta()) {
|
||||||
log('Detected Rosetta, skipping search for globally-installed libvips');
|
return skipSearch(false, 'Rosetta', logger);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const globalVipsVersion = globalLibvipsVersion();
|
const globalVipsVersion = globalLibvipsVersion();
|
||||||
return !!globalVipsVersion && /* istanbul ignore next */
|
return !!globalVipsVersion && /* istanbul ignore next */
|
||||||
@@ -144,8 +192,10 @@ module.exports = {
|
|||||||
buildSharpLibvipsIncludeDir,
|
buildSharpLibvipsIncludeDir,
|
||||||
buildSharpLibvipsCPlusPlusDir,
|
buildSharpLibvipsCPlusPlusDir,
|
||||||
buildSharpLibvipsLibDir,
|
buildSharpLibvipsLibDir,
|
||||||
|
isUnsupportedNodeRuntime,
|
||||||
runtimePlatformArch,
|
runtimePlatformArch,
|
||||||
log,
|
log,
|
||||||
|
yarnLocator,
|
||||||
spawnRebuild,
|
spawnRebuild,
|
||||||
globalLibvipsVersion,
|
globalLibvipsVersion,
|
||||||
pkgConfigPath,
|
pkgConfigPath,
|
||||||
|
|||||||
@@ -6,6 +6,17 @@
|
|||||||
const color = require('color');
|
const color = require('color');
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How accurate an operation should be.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const vipsPrecision = {
|
||||||
|
integer: 'integer',
|
||||||
|
float: 'float',
|
||||||
|
approximate: 'approximate'
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate the output image by either an explicit angle
|
* Rotate the output image by either an explicit angle
|
||||||
* or auto-orient based on the EXIF `Orientation` tag.
|
* or auto-orient based on the EXIF `Orientation` tag.
|
||||||
@@ -24,6 +35,8 @@ const is = require('./is');
|
|||||||
* Only one rotation can occur per pipeline.
|
* Only one rotation can occur per pipeline.
|
||||||
* Previous calls to `rotate` in the same pipeline will be ignored.
|
* Previous calls to `rotate` in the same pipeline will be ignored.
|
||||||
*
|
*
|
||||||
|
* Multi-page images can only be rotated by 180 degrees.
|
||||||
|
*
|
||||||
* Method order is important when rotating, resizing and/or extracting regions,
|
* 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)`.
|
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
||||||
*
|
*
|
||||||
@@ -365,23 +378,51 @@ function median (size) {
|
|||||||
* .blur(5)
|
* .blur(5)
|
||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
* @param {Object|number|Boolean} [options]
|
||||||
|
* @param {number} [options.sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
||||||
|
* @param {string} [options.precision='integer'] How accurate the operation should be, one of: integer, float, approximate.
|
||||||
|
* @param {number} [options.minAmplitude=0.2] A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function blur (sigma) {
|
function blur (options) {
|
||||||
if (!is.defined(sigma)) {
|
let sigma;
|
||||||
|
if (is.number(options)) {
|
||||||
|
sigma = options;
|
||||||
|
} else if (is.plainObject(options)) {
|
||||||
|
if (!is.number(options.sigma)) {
|
||||||
|
throw is.invalidParameterError('options.sigma', 'number between 0.3 and 1000', sigma);
|
||||||
|
}
|
||||||
|
sigma = options.sigma;
|
||||||
|
if ('precision' in options) {
|
||||||
|
if (is.string(vipsPrecision[options.precision])) {
|
||||||
|
this.options.precision = vipsPrecision[options.precision];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('precision', 'one of: integer, float, approximate', options.precision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('minAmplitude' in options) {
|
||||||
|
if (is.number(options.minAmplitude) && is.inRange(options.minAmplitude, 0.001, 1)) {
|
||||||
|
this.options.minAmpl = options.minAmplitude;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('minAmplitude', 'number between 0.001 and 1', options.minAmplitude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.defined(options)) {
|
||||||
// No arguments: default to mild blur
|
// No arguments: default to mild blur
|
||||||
this.options.blurSigma = -1;
|
this.options.blurSigma = -1;
|
||||||
} else if (is.bool(sigma)) {
|
} else if (is.bool(options)) {
|
||||||
// Boolean argument: apply mild blur?
|
// Boolean argument: apply mild blur?
|
||||||
this.options.blurSigma = sigma ? -1 : 0;
|
this.options.blurSigma = options ? -1 : 0;
|
||||||
} else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
|
} else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
|
||||||
// Numeric argument: specific sigma
|
// Numeric argument: specific sigma
|
||||||
this.options.blurSigma = sigma;
|
this.options.blurSigma = sigma;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
|
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,24 +826,22 @@ function linear (a, b) {
|
|||||||
* // With this example input, a sepia filter has been applied
|
* // With this example input, a sepia filter has been applied
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
|
* @param {Array<Array<number>>} inputMatrix - 3x3 or 4x4 Recombination matrix
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function recomb (inputMatrix) {
|
function recomb (inputMatrix) {
|
||||||
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
|
if (!Array.isArray(inputMatrix)) {
|
||||||
inputMatrix[0].length !== 3 ||
|
throw is.invalidParameterError('inputMatrix', 'array', inputMatrix);
|
||||||
inputMatrix[1].length !== 3 ||
|
|
||||||
inputMatrix[2].length !== 3
|
|
||||||
) {
|
|
||||||
// must pass in a kernel
|
|
||||||
throw new Error('Invalid recombination matrix');
|
|
||||||
}
|
}
|
||||||
this.options.recombMatrix = [
|
if (inputMatrix.length !== 3 && inputMatrix.length !== 4) {
|
||||||
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
|
throw is.invalidParameterError('inputMatrix', '3x3 or 4x4 array', inputMatrix.length);
|
||||||
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
|
}
|
||||||
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
|
const recombMatrix = inputMatrix.flat().map(Number);
|
||||||
].map(Number);
|
if (recombMatrix.length !== 9 && recombMatrix.length !== 16) {
|
||||||
|
throw is.invalidParameterError('inputMatrix', 'cardinality of 9 or 16', recombMatrix.length);
|
||||||
|
}
|
||||||
|
this.options.recombMatrix = recombMatrix;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
261
lib/output.js
@@ -65,6 +65,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
|
|||||||
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
||||||
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||||
* When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region.
|
* When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region.
|
||||||
|
* Animated output will also contain `pageHeight` and `pages`.
|
||||||
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
||||||
* @returns {Promise<Object>} - when no callback is provided
|
* @returns {Promise<Object>} - when no callback is provided
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
@@ -109,6 +110,7 @@ function toFile (fileOut, callback) {
|
|||||||
* - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
* - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
|
||||||
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
* `channels` and `premultiplied` (indicating if premultiplication was used).
|
||||||
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
|
||||||
|
* Animated output will also contain `pageHeight` and `pages`.
|
||||||
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
|
* 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.
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
@@ -163,39 +165,185 @@ function toBuffer (options, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
* Keep all EXIF metadata from the input image in the output image.
|
||||||
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
|
||||||
* unless a custom 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.
|
* EXIF metadata is unsupported for TIFF output.
|
||||||
*
|
*
|
||||||
* @example
|
* @since 0.33.0
|
||||||
* sharp('input.jpg')
|
|
||||||
* .withMetadata()
|
|
||||||
* .toFile('output-with-metadata.jpg')
|
|
||||||
* .then(info => { ... });
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // Set output EXIF metadata
|
* const outputWithExif = await sharp(inputWithExif)
|
||||||
* const data = await sharp(input)
|
* .keepExif()
|
||||||
* .withMetadata({
|
* .toBuffer();
|
||||||
* exif: {
|
*
|
||||||
* IFD0: {
|
* @returns {Sharp}
|
||||||
* Copyright: 'The National Gallery'
|
*/
|
||||||
* },
|
function keepExif () {
|
||||||
* IFD3: {
|
this.options.keepMetadata |= 0b00001;
|
||||||
* GPSLatitudeRef: 'N',
|
return this;
|
||||||
* GPSLatitude: '51/1 30/1 3230/100',
|
}
|
||||||
* GPSLongitudeRef: 'W',
|
|
||||||
* GPSLongitude: '0/1 7/1 4366/100'
|
/**
|
||||||
* }
|
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||||
|
*
|
||||||
|
* @since 0.33.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const dataWithExif = await sharp(input)
|
||||||
|
* .withExif({
|
||||||
|
* IFD0: {
|
||||||
|
* Copyright: 'The National Gallery'
|
||||||
|
* },
|
||||||
|
* IFD3: {
|
||||||
|
* GPSLatitudeRef: 'N',
|
||||||
|
* GPSLatitude: '51/1 30/1 3230/100',
|
||||||
|
* GPSLongitudeRef: 'W',
|
||||||
|
* GPSLongitude: '0/1 7/1 4366/100'
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
|
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function withExif (exif) {
|
||||||
|
if (is.object(exif)) {
|
||||||
|
for (const [ifd, entries] of Object.entries(exif)) {
|
||||||
|
if (is.object(entries)) {
|
||||||
|
for (const [k, v] of Object.entries(entries)) {
|
||||||
|
if (is.string(v)) {
|
||||||
|
this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError(ifd, 'object', entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('exif', 'object', exif);
|
||||||
|
}
|
||||||
|
this.options.withExifMerge = false;
|
||||||
|
return this.keepExif();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update EXIF metadata from the input image in the output image.
|
||||||
|
*
|
||||||
|
* @since 0.33.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const dataWithMergedExif = await sharp(inputWithExif)
|
||||||
|
* .withExifMerge({
|
||||||
|
* IFD0: {
|
||||||
|
* Copyright: 'The National Gallery'
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function withExifMerge (exif) {
|
||||||
|
this.withExif(exif);
|
||||||
|
this.options.withExifMerge = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep ICC profile from the input image in the output image.
|
||||||
|
*
|
||||||
|
* Where necessary, will attempt to convert the output colour space to match the profile.
|
||||||
|
*
|
||||||
|
* @since 0.33.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||||
|
* .keepIccProfile()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function keepIccProfile () {
|
||||||
|
this.options.keepMetadata |= 0b01000;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform using an ICC profile and attach to the output image.
|
||||||
|
*
|
||||||
|
* This can either be an absolute filesystem path or
|
||||||
|
* built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||||
|
*
|
||||||
|
* @since 0.33.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const outputWithP3 = await sharp(input)
|
||||||
|
* .withIccProfile('p3')
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function withIccProfile (icc, options) {
|
||||||
|
if (is.string(icc)) {
|
||||||
|
this.options.withIccProfile = icc;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('icc', 'string', icc);
|
||||||
|
}
|
||||||
|
this.keepIccProfile();
|
||||||
|
if (is.object(options)) {
|
||||||
|
if (is.defined(options.attach)) {
|
||||||
|
if (is.bool(options.attach)) {
|
||||||
|
if (!options.attach) {
|
||||||
|
this.options.keepMetadata &= ~0b01000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('attach', 'boolean', options.attach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||||
|
*
|
||||||
|
* The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||||
|
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||||
|
*
|
||||||
|
* @since 0.33.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const outputWithMetadata = await sharp(inputWithMetadata)
|
||||||
|
* .keepMetadata()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @returns {Sharp}
|
||||||
|
*/
|
||||||
|
function keepMetadata () {
|
||||||
|
this.options.keepMetadata = 0b11111;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||||
|
*
|
||||||
|
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||||
|
*
|
||||||
|
* Allows orientation and density to be set or updated.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||||
|
* .withMetadata()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* // Set output metadata to 96 DPI
|
* // Set output metadata to 96 DPI
|
||||||
* const data = await sharp(input)
|
* const data = await sharp(input)
|
||||||
@@ -203,15 +351,14 @@ function toBuffer (options, callback) {
|
|||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
* @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
|
||||||
* @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
|
|
||||||
* @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
|
||||||
* @param {number} [options.density] Number of pixels per inch (DPI).
|
* @param {number} [options.density] Number of pixels per inch (DPI).
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function withMetadata (options) {
|
function withMetadata (options) {
|
||||||
this.options.withMetadata = is.bool(options) ? options : true;
|
this.keepMetadata();
|
||||||
|
this.withIccProfile('srgb');
|
||||||
if (is.object(options)) {
|
if (is.object(options)) {
|
||||||
if (is.defined(options.orientation)) {
|
if (is.defined(options.orientation)) {
|
||||||
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
||||||
@@ -228,30 +375,10 @@ function withMetadata (options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(options.icc)) {
|
if (is.defined(options.icc)) {
|
||||||
if (is.string(options.icc)) {
|
this.withIccProfile(options.icc);
|
||||||
this.options.withMetadataIcc = options.icc;
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (is.defined(options.exif)) {
|
if (is.defined(options.exif)) {
|
||||||
if (is.object(options.exif)) {
|
this.withExifMerge(options.exif);
|
||||||
for (const [ifd, entries] of Object.entries(options.exif)) {
|
|
||||||
if (is.object(entries)) {
|
|
||||||
for (const [k, v] of Object.entries(entries)) {
|
|
||||||
if (is.string(v)) {
|
|
||||||
this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError('exif', 'object', options.exif);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@@ -379,10 +506,14 @@ function jpeg (options) {
|
|||||||
/**
|
/**
|
||||||
* Use these PNG options for output image.
|
* Use these PNG options for output image.
|
||||||
*
|
*
|
||||||
* By default, PNG output is full colour at 8 or 16 bits per pixel.
|
* By default, PNG output is full colour at 8 bits per pixel.
|
||||||
|
*
|
||||||
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
||||||
* Set `palette` to `true` for slower, indexed PNG output.
|
* Set `palette` to `true` for slower, indexed PNG output.
|
||||||
*
|
*
|
||||||
|
* For 16 bits per pixel output, convert to `rgb16` via
|
||||||
|
* {@link /api-colour#tocolourspace|toColourspace}.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* // Convert any input to full colour PNG output
|
* // Convert any input to full colour PNG output
|
||||||
* const data = await sharp(input)
|
* const data = await sharp(input)
|
||||||
@@ -395,6 +526,13 @@ function jpeg (options) {
|
|||||||
* .png({ palette: true })
|
* .png({ palette: true })
|
||||||
* .toBuffer();
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* // Output 16 bits per pixel RGB(A)
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .toColourspace('rgb16')
|
||||||
|
* .png()
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
|
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||||
* @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
|
* @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
|
||||||
@@ -875,6 +1013,7 @@ function tiff (options) {
|
|||||||
* Use these AVIF options for output image.
|
* Use these AVIF options for output image.
|
||||||
*
|
*
|
||||||
* AVIF image sequences are not supported.
|
* AVIF image sequences are not supported.
|
||||||
|
* Prebuilt binaries support a bitdepth of 8 only.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const data = await sharp(input)
|
* const data = await sharp(input)
|
||||||
@@ -893,6 +1032,7 @@ function tiff (options) {
|
|||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
|
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@@ -919,6 +1059,7 @@ function avif (options) {
|
|||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
|
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@@ -957,6 +1098,16 @@ function heif (options) {
|
|||||||
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.bitdepth)) {
|
||||||
|
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [8, 10, 12])) {
|
||||||
|
if (options.bitdepth !== 8 && this.constructor.versions.heif) {
|
||||||
|
throw is.invalidParameterError('bitdepth when using prebuilt binaries', 8, options.bitdepth);
|
||||||
|
}
|
||||||
|
this.options.heifBitdepth = options.bitdepth;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('options', 'Object', options);
|
throw is.invalidParameterError('options', 'Object', options);
|
||||||
}
|
}
|
||||||
@@ -1108,7 +1259,7 @@ function raw (options) {
|
|||||||
* @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
|
* @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
|
||||||
* @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
|
* @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
|
||||||
* @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
* @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
|
||||||
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
* @param {number} [options.skipBlanks=-1] Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise.
|
||||||
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||||
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
|
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
|
||||||
* @param {boolean} [options.centre=false] centre image in tile.
|
* @param {boolean} [options.centre=false] centre image in tile.
|
||||||
@@ -1407,6 +1558,12 @@ module.exports = function (Sharp) {
|
|||||||
// Public
|
// Public
|
||||||
toFile,
|
toFile,
|
||||||
toBuffer,
|
toBuffer,
|
||||||
|
keepExif,
|
||||||
|
withExif,
|
||||||
|
withExifMerge,
|
||||||
|
keepIccProfile,
|
||||||
|
withIccProfile,
|
||||||
|
keepMetadata,
|
||||||
withMetadata,
|
withMetadata,
|
||||||
toFormat,
|
toFormat,
|
||||||
jpeg,
|
jpeg,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const strategy = {
|
|||||||
*/
|
*/
|
||||||
const kernel = {
|
const kernel = {
|
||||||
nearest: 'nearest',
|
nearest: 'nearest',
|
||||||
|
linear: 'linear',
|
||||||
cubic: 'cubic',
|
cubic: 'cubic',
|
||||||
mitchell: 'mitchell',
|
mitchell: 'mitchell',
|
||||||
lanczos2: 'lanczos2',
|
lanczos2: 'lanczos2',
|
||||||
@@ -135,18 +136,22 @@ function isResizeExpected (options) {
|
|||||||
*
|
*
|
||||||
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
||||||
*
|
*
|
||||||
* The experimental strategy-based approach resizes so one dimension is at its target length
|
* The strategy-based approach initially resizes so one dimension is at its target length
|
||||||
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
||||||
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
||||||
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
||||||
*
|
*
|
||||||
* Possible interpolation kernels are:
|
* Possible downsizing kernels are:
|
||||||
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
||||||
|
* - `linear`: Use a [triangle filter](https://en.wikipedia.org/wiki/Triangular_function).
|
||||||
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
||||||
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
||||||
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
* - `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).
|
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
||||||
*
|
*
|
||||||
|
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
|
||||||
|
* Downsampling kernels without a matching upsampling interpolator map to `cubic`.
|
||||||
|
*
|
||||||
* Only one resize can occur per pipeline.
|
* Only one resize can occur per pipeline.
|
||||||
* Previous calls to `resize` in the same pipeline will be ignored.
|
* Previous calls to `resize` in the same pipeline will be ignored.
|
||||||
*
|
*
|
||||||
@@ -239,7 +244,7 @@ function isResizeExpected (options) {
|
|||||||
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
||||||
* @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
|
* @param {String} [options.position='centre'] - A 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 `fit` is `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. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
|
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction and the inferred interpolator to use for upsampling. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
|
||||||
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
|
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
|
||||||
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
|
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
|
||||||
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
|
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
|
||||||
|
|||||||
155
lib/sharp.js
@@ -7,63 +7,108 @@
|
|||||||
|
|
||||||
const { familySync, versionSync } = require('detect-libc');
|
const { familySync, versionSync } = require('detect-libc');
|
||||||
|
|
||||||
const { runtimePlatformArch, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
|
const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
|
||||||
const runtimePlatform = runtimePlatformArch();
|
const runtimePlatform = runtimePlatformArch();
|
||||||
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
|
||||||
|
|
||||||
/* istanbul ignore next */
|
const paths = [
|
||||||
try {
|
`../src/build/Release/sharp-${runtimePlatform}.node`,
|
||||||
// Check for local build
|
'../src/build/Release/sharp-wasm32.node',
|
||||||
module.exports = require(`../src/build/Release/sharp-${runtimePlatform}.node`);
|
`@img/sharp-${runtimePlatform}/sharp.node`,
|
||||||
} catch (errLocal) {
|
'@img/sharp-wasm32/sharp.node'
|
||||||
|
];
|
||||||
|
|
||||||
|
let sharp;
|
||||||
|
const errors = [];
|
||||||
|
for (const path of paths) {
|
||||||
try {
|
try {
|
||||||
// Check for runtime package
|
sharp = require(path);
|
||||||
module.exports = require(`@img/sharp-${runtimePlatform}/sharp.node`);
|
break;
|
||||||
} catch (errPackage) {
|
} catch (err) {
|
||||||
const help = ['Could not load the "sharp" module at runtime'];
|
/* istanbul ignore next */
|
||||||
if (errLocal.code !== 'MODULE_NOT_FOUND') {
|
errors.push(err);
|
||||||
help.push(`${errLocal.code}: ${errLocal.message}`);
|
|
||||||
}
|
|
||||||
if (errPackage.code !== 'MODULE_NOT_FOUND') {
|
|
||||||
help.push(`${errPackage.code}: ${errPackage.message}`);
|
|
||||||
}
|
|
||||||
help.push('Possible solutions:');
|
|
||||||
// Common error messages
|
|
||||||
if (prebuiltPlatforms.includes(runtimePlatform)) {
|
|
||||||
help.push('- Add an explicit dependency for the runtime platform:');
|
|
||||||
help.push(` npm install --force @img/sharp-${runtimePlatform}`);
|
|
||||||
} else {
|
|
||||||
help.push(`- The ${runtimePlatform} platform requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
|
||||||
}
|
|
||||||
if (isLinux && /symbol not found/i.test(errPackage)) {
|
|
||||||
try {
|
|
||||||
const { engines } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
|
|
||||||
const libcFound = `${familySync()} ${versionSync()}`;
|
|
||||||
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
|
|
||||||
help.push('- Update your OS:');
|
|
||||||
help.push(` Found ${libcFound}`);
|
|
||||||
help.push(` Requires ${libcRequires}`);
|
|
||||||
} catch (errEngines) {}
|
|
||||||
}
|
|
||||||
if (isMacOs && /Incompatible library version/.test(errLocal.message)) {
|
|
||||||
help.push('- Update Homebrew:');
|
|
||||||
help.push(' brew update && brew upgrade vips');
|
|
||||||
}
|
|
||||||
if (errPackage.code === 'ERR_DLOPEN_DISABLED') {
|
|
||||||
help.push('- Run Node.js without using the --no-addons flag');
|
|
||||||
}
|
|
||||||
if (process.versions.pnp) {
|
|
||||||
help.push('- Use a supported yarn linker, either pnpm or node-modules:');
|
|
||||||
help.push(' yarn config set nodeLinker node-modules');
|
|
||||||
}
|
|
||||||
// Link to installation docs
|
|
||||||
if (isLinux && /Module did not self-register/.test(errLocal.message + errPackage.message)) {
|
|
||||||
help.push('- Using worker threads on Linux? See https://sharp.pixelplumbing.com/install#worker-threads');
|
|
||||||
} else if (isWindows && /The specified procedure could not be found/.test(errPackage.message)) {
|
|
||||||
help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
|
|
||||||
} else {
|
|
||||||
help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
|
|
||||||
}
|
|
||||||
throw new Error(help.join('\n'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (sharp) {
|
||||||
|
module.exports = sharp;
|
||||||
|
} else {
|
||||||
|
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
||||||
|
|
||||||
|
const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
|
||||||
|
errors.forEach(err => {
|
||||||
|
if (err.code !== 'MODULE_NOT_FOUND') {
|
||||||
|
help.push(`${err.code}: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const messages = errors.map(err => err.message).join(' ');
|
||||||
|
help.push('Possible solutions:');
|
||||||
|
// Common error messages
|
||||||
|
if (isUnsupportedNodeRuntime()) {
|
||||||
|
const { found, expected } = isUnsupportedNodeRuntime();
|
||||||
|
help.push(
|
||||||
|
'- Please upgrade Node.js:',
|
||||||
|
` Found ${found}`,
|
||||||
|
` Requires ${expected}`
|
||||||
|
);
|
||||||
|
} else if (prebuiltPlatforms.includes(runtimePlatform)) {
|
||||||
|
const [os, cpu] = runtimePlatform.split('-');
|
||||||
|
const libc = os.endsWith('musl') ? ' --libc=musl' : '';
|
||||||
|
help.push(
|
||||||
|
'- Ensure optional dependencies can be installed:',
|
||||||
|
' npm install --include=optional sharp',
|
||||||
|
'- Ensure your package manager supports multi-platform installation:',
|
||||||
|
' See https://sharp.pixelplumbing.com/install#cross-platform',
|
||||||
|
'- Add platform-specific dependencies:',
|
||||||
|
` npm install --os=${os.replace('musl', '')}${libc} --cpu=${cpu} sharp`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
help.push(
|
||||||
|
`- Manually install libvips >= ${minimumLibvipsVersion}`,
|
||||||
|
'- Add experimental WebAssembly-based dependencies:',
|
||||||
|
' npm install --cpu=wasm32 sharp',
|
||||||
|
' npm install @img/sharp-wasm32'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isLinux && /(symbol not found|CXXABI_)/i.test(messages)) {
|
||||||
|
try {
|
||||||
|
const { config } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
|
||||||
|
const libcFound = `${familySync()} ${versionSync()}`;
|
||||||
|
const libcRequires = `${config.musl ? 'musl' : 'glibc'} ${config.musl || config.glibc}`;
|
||||||
|
help.push(
|
||||||
|
'- Update your OS:',
|
||||||
|
` Found ${libcFound}`,
|
||||||
|
` Requires ${libcRequires}`
|
||||||
|
);
|
||||||
|
} catch (errEngines) {}
|
||||||
|
}
|
||||||
|
if (isLinux && /\/snap\/core[0-9]{2}/.test(messages)) {
|
||||||
|
help.push(
|
||||||
|
'- Remove the Node.js Snap, which does not support native modules',
|
||||||
|
' snap remove node'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isMacOs && /Incompatible library version/.test(messages)) {
|
||||||
|
help.push(
|
||||||
|
'- Update Homebrew:',
|
||||||
|
' brew update && brew upgrade vips'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (errors.some(err => err.code === 'ERR_DLOPEN_DISABLED')) {
|
||||||
|
help.push('- Run Node.js without using the --no-addons flag');
|
||||||
|
}
|
||||||
|
// Link to installation docs
|
||||||
|
if (isWindows && /The specified procedure could not be found/.test(messages)) {
|
||||||
|
help.push(
|
||||||
|
'- Using the canvas package on Windows?',
|
||||||
|
' See https://sharp.pixelplumbing.com/install#canvas-and-windows',
|
||||||
|
'- Check for outdated versions of sharp in the dependency tree:',
|
||||||
|
' npm ls sharp'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
help.push(
|
||||||
|
'- Consult the installation documentation:',
|
||||||
|
' See https://sharp.pixelplumbing.com/install'
|
||||||
|
);
|
||||||
|
throw new Error(help.join('\n'));
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,16 +59,29 @@ let versions = {
|
|||||||
};
|
};
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!libvipsVersion.isGlobal) {
|
if (!libvipsVersion.isGlobal) {
|
||||||
try {
|
if (!libvipsVersion.isWasm) {
|
||||||
versions = require(`@img/sharp-${runtimePlatform}/versions`);
|
|
||||||
} catch (_) {
|
|
||||||
try {
|
try {
|
||||||
versions = require(`@img/sharp-libvips-${runtimePlatform}/versions`);
|
versions = require(`@img/sharp-${runtimePlatform}/versions`);
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
versions = require(`@img/sharp-libvips-${runtimePlatform}/versions`);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
versions = require('@img/sharp-wasm32/versions');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
versions.sharp = require('../package.json').version;
|
versions.sharp = require('../package.json').version;
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (versions.heif && format.heif) {
|
||||||
|
// Prebuilt binaries provide AV1
|
||||||
|
format.heif.input.fileSuffix = ['.avif'];
|
||||||
|
format.heif.output.alias = ['avif'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
|
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
|
||||||
* Existing entries in the cache will be trimmed after any change in limits.
|
* Existing entries in the cache will be trimmed after any change in limits.
|
||||||
@@ -147,6 +160,9 @@ function concurrency (concurrency) {
|
|||||||
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
|
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
|
||||||
// Reduce default concurrency to 1 when using glibc memory allocator
|
// Reduce default concurrency to 1 when using glibc memory allocator
|
||||||
sharp.concurrency(1);
|
sharp.concurrency(1);
|
||||||
|
} else if (detectLibc.familySync() === detectLibc.MUSL && sharp.concurrency() === 1024) {
|
||||||
|
// Reduce default concurrency when musl thread over-subscription detected
|
||||||
|
sharp.concurrency(require('node:os').availableParallelism());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,17 +193,17 @@ function counters () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get and set use of SIMD vector unit instructions.
|
* Get and set use of SIMD vector unit instructions.
|
||||||
* Requires libvips to have been compiled with liborc support.
|
* Requires libvips to have been compiled with highway support.
|
||||||
*
|
*
|
||||||
* Improves the performance of `resize`, `blur` and `sharpen` operations
|
* Improves the performance of `resize`, `blur` and `sharpen` operations
|
||||||
* by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
* by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const simd = sharp.simd();
|
* const simd = sharp.simd();
|
||||||
* // simd is `true` if the runtime use of liborc is currently enabled
|
* // simd is `true` if the runtime use of highway is currently enabled
|
||||||
* @example
|
* @example
|
||||||
* const simd = sharp.simd(false);
|
* const simd = sharp.simd(false);
|
||||||
* // prevent libvips from using liborc at runtime
|
* // prevent libvips from using highway at runtime
|
||||||
*
|
*
|
||||||
* @param {boolean} [simd=true]
|
* @param {boolean} [simd=true]
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -195,7 +211,6 @@ function counters () {
|
|||||||
function simd (simd) {
|
function simd (simd) {
|
||||||
return sharp.simd(is.bool(simd) ? simd : null);
|
return sharp.simd(is.bool(simd) ? simd : null);
|
||||||
}
|
}
|
||||||
simd(true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block libvips operations at runtime.
|
* Block libvips operations at runtime.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-darwin-arm64",
|
"name": "@img/sharp-darwin-arm64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
|
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-darwin-arm64": "0.0.3"
|
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,11 +29,7 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
|
||||||
"yarn": ">=3.2.0",
|
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"glibc": ">=2.26"
|
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-darwin-x64",
|
"name": "@img/sharp-darwin-x64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with macOS x64",
|
"description": "Prebuilt sharp for use with macOS x64",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-darwin-x64": "0.0.3"
|
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,11 +29,7 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
|
||||||
"yarn": ">=3.2.0",
|
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"glibc": ">=2.26"
|
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ limitations under the License.
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
workspaces.map(async platform => {
|
workspaces.map(async platform => {
|
||||||
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${platform}.tar.gz`;
|
const prebuildPlatform = platform === 'wasm32' ? 'emscripten-wasm32' : platform;
|
||||||
|
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${prebuildPlatform}.tar.gz`;
|
||||||
const dir = path.join(__dirname, platform);
|
const dir = path.join(__dirname, platform);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -47,7 +48,7 @@ workspaces.map(async platform => {
|
|||||||
}
|
}
|
||||||
// Extract prebuild tarball
|
// Extract prebuild tarball
|
||||||
const lib = path.join(dir, 'lib');
|
const lib = path.join(dir, 'lib');
|
||||||
await rm(lib, { recursive: true });
|
await rm(lib, { force: true, recursive: true });
|
||||||
await pipeline(
|
await pipeline(
|
||||||
Readable.fromWeb(response.body),
|
Readable.fromWeb(response.body),
|
||||||
createGunzip(),
|
createGunzip(),
|
||||||
@@ -58,9 +59,10 @@ workspaces.map(async platform => {
|
|||||||
await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
|
await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
|
||||||
// Copy Apache-2.0 LICENSE
|
// Copy Apache-2.0 LICENSE
|
||||||
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
|
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
|
||||||
// Copy Windows-specific files
|
// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm)
|
||||||
if (platform.startsWith('win32-')) {
|
if (platform.startsWith('win') || platform.startsWith('wasm')) {
|
||||||
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${platform}/lib`), '..');
|
const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform;
|
||||||
|
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..');
|
||||||
// Copy versions.json
|
// Copy versions.json
|
||||||
await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json'));
|
await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json'));
|
||||||
// Append third party licensing to README
|
// Append third party licensing to README
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linux-arm",
|
"name": "@img/sharp-linux-arm",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
|
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linux-arm": "0.0.3"
|
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,10 +29,9 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"glibc": ">=2.28"
|
"glibc": ">=2.28"
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linux-arm64",
|
"name": "@img/sharp-linux-arm64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
|
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linux-arm64": "0.0.3"
|
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,10 +29,9 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"glibc": ">=2.26"
|
"glibc": ">=2.26"
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linux-s390x",
|
"name": "@img/sharp-linux-s390x",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (glibc) s390x",
|
"description": "Prebuilt sharp for use with Linux (glibc) s390x",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linux-s390x": "0.0.3"
|
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,11 +29,10 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
"glibc": ">=2.31"
|
||||||
"glibc": ">=2.28"
|
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linux-x64",
|
"name": "@img/sharp-linux-x64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linux-x64": "0.0.3"
|
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,10 +29,9 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"glibc": ">=2.26"
|
"glibc": ">=2.26"
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linuxmusl-arm64",
|
"name": "@img/sharp-linuxmusl-arm64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
|
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3"
|
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,10 +29,9 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"musl": ">=1.2.2"
|
"musl": ">=1.2.2"
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-linuxmusl-x64",
|
"name": "@img/sharp-linuxmusl-x64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3"
|
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
@@ -29,10 +29,9 @@
|
|||||||
"./package": "./package.json"
|
"./package": "./package.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
},
|
||||||
"yarn": ">=3.2.0",
|
"config": {
|
||||||
"pnpm": ">=7.1.0",
|
|
||||||
"musl": ">=1.2.2"
|
"musl": ">=1.2.2"
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp",
|
"name": "@img/sharp",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"darwin-arm64",
|
"darwin-arm64",
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"linux-x64",
|
"linux-x64",
|
||||||
"linuxmusl-arm64",
|
"linuxmusl-arm64",
|
||||||
"linuxmusl-x64",
|
"linuxmusl-x64",
|
||||||
|
"wasm32",
|
||||||
"win32-ia32",
|
"win32-ia32",
|
||||||
"win32-x64"
|
"win32-x64"
|
||||||
]
|
]
|
||||||
|
|||||||
39
npm/wasm32/package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@img/sharp-wasm32",
|
||||||
|
"version": "0.33.5",
|
||||||
|
"description": "Prebuilt sharp for use with wasm32",
|
||||||
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/lovell/sharp.git",
|
||||||
|
"directory": "npm/wasm32"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"versions.json"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
|
"exports": {
|
||||||
|
"./sharp.node": "./lib/sharp-wasm32.node.js",
|
||||||
|
"./package": "./package.json",
|
||||||
|
"./versions": "./versions.json"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/runtime": "^1.2.0"
|
||||||
|
},
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-win32-ia32",
|
"name": "@img/sharp-win32-ia32",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
|
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -28,10 +28,7 @@
|
|||||||
"./versions": "./versions.json"
|
"./versions": "./versions.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
|
||||||
"yarn": ">=3.2.0",
|
|
||||||
"pnpm": ">=7.1.0"
|
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@img/sharp-win32-x64",
|
"name": "@img/sharp-win32-x64",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"description": "Prebuilt sharp for use with Windows x64",
|
"description": "Prebuilt sharp for use with Windows x64",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://sharp.pixelplumbing.com",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
@@ -28,10 +28,7 @@
|
|||||||
"./versions": "./versions.json"
|
"./versions": "./versions.json"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"npm": ">=9.6.5",
|
|
||||||
"yarn": ">=3.2.0",
|
|
||||||
"pnpm": ">=7.1.0"
|
|
||||||
},
|
},
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
|
|||||||
89
package.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
||||||
"version": "0.33.0-alpha.10",
|
"version": "0.33.5",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://github.com/lovell/sharp",
|
"homepage": "https://sharp.pixelplumbing.com",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||||
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
||||||
@@ -82,12 +82,14 @@
|
|||||||
"Joris Dugué <zaruike10@gmail.com>",
|
"Joris Dugué <zaruike10@gmail.com>",
|
||||||
"Chris Banks <christopher.bradley.banks@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",
|
"Brodan <christopher.hranj@gmail.com>",
|
||||||
"Ankur Parihar <ankur.github@gmail.com>",
|
"Ankur Parihar <ankur.github@gmail.com>",
|
||||||
"Brahim Ait elhaj <brahima@gmail.com>",
|
"Brahim Ait elhaj <brahima@gmail.com>",
|
||||||
"Mart Jansink <m.jansink@gmail.com>",
|
"Mart Jansink <m.jansink@gmail.com>",
|
||||||
"Lachlan Newman <lachnewman007@gmail.com>",
|
"Lachlan Newman <lachnewman007@gmail.com>",
|
||||||
"Dennis Beatty <dennis@dcbeatty.com>"
|
"Dennis Beatty <dennis@dcbeatty.com>",
|
||||||
|
"Ingvar Stepanyan <me@rreverser.com>",
|
||||||
|
"Don Denton <don@happycollision.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "node install/check",
|
"install": "node install/check",
|
||||||
@@ -136,53 +138,59 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"detect-libc": "^2.0.2",
|
"detect-libc": "^2.0.3",
|
||||||
"semver": "^7.5.4"
|
"semver": "^7.6.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@img/sharp-darwin-arm64": "0.33.0-alpha.10",
|
"@img/sharp-darwin-arm64": "0.33.5",
|
||||||
"@img/sharp-darwin-x64": "0.33.0-alpha.10",
|
"@img/sharp-darwin-x64": "0.33.5",
|
||||||
"@img/sharp-libvips-darwin-arm64": "0.0.3",
|
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||||
"@img/sharp-libvips-darwin-x64": "0.0.3",
|
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||||
"@img/sharp-libvips-linux-arm": "0.0.3",
|
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||||
"@img/sharp-libvips-linux-arm64": "0.0.3",
|
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||||
"@img/sharp-libvips-linux-s390x": "0.0.3",
|
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||||
"@img/sharp-libvips-linux-x64": "0.0.3",
|
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3",
|
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3",
|
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||||
"@img/sharp-linux-arm": "0.33.0-alpha.10",
|
"@img/sharp-linux-arm": "0.33.5",
|
||||||
"@img/sharp-linux-arm64": "0.33.0-alpha.10",
|
"@img/sharp-linux-arm64": "0.33.5",
|
||||||
"@img/sharp-linux-s390x": "0.33.0-alpha.10",
|
"@img/sharp-linux-s390x": "0.33.5",
|
||||||
"@img/sharp-linux-x64": "0.33.0-alpha.10",
|
"@img/sharp-linux-x64": "0.33.5",
|
||||||
"@img/sharp-linuxmusl-arm64": "0.33.0-alpha.10",
|
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||||
"@img/sharp-linuxmusl-x64": "0.33.0-alpha.10",
|
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||||
"@img/sharp-win32-ia32": "0.33.0-alpha.10",
|
"@img/sharp-wasm32": "0.33.5",
|
||||||
"@img/sharp-win32-x64": "0.33.0-alpha.10"
|
"@img/sharp-win32-ia32": "0.33.5",
|
||||||
|
"@img/sharp-win32-x64": "0.33.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@img/sharp-libvips-dev": "0.0.3",
|
"@emnapi/runtime": "^1.2.0",
|
||||||
"@img/sharp-libvips-win32-ia32": "0.0.3",
|
"@img/sharp-libvips-dev": "1.0.4",
|
||||||
"@img/sharp-libvips-win32-x64": "0.0.3",
|
"@img/sharp-libvips-dev-wasm32": "1.0.5",
|
||||||
|
"@img/sharp-libvips-win32-ia32": "1.0.4",
|
||||||
|
"@img/sharp-libvips-win32-x64": "1.0.4",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"async": "^3.2.4",
|
"async": "^3.2.5",
|
||||||
"cc": "^3.0.1",
|
"cc": "^3.0.1",
|
||||||
"exif-reader": "^2.0.0",
|
"emnapi": "^1.2.0",
|
||||||
|
"exif-reader": "^2.0.1",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"icc": "^3.0.0",
|
"icc": "^3.0.0",
|
||||||
"jsdoc-to-markdown": "^8.0.0",
|
"jsdoc-to-markdown": "^8.0.3",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.7.3",
|
||||||
"node-addon-api": "^7.0.0",
|
"node-addon-api": "^8.1.0",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^17.0.0",
|
||||||
"prebuild": "^12.1.0",
|
"prebuild": "^13.0.1",
|
||||||
"semistandard": "^17.0.0",
|
"semistandard": "^17.0.0",
|
||||||
"tar-fs": "^3.0.4",
|
"tar-fs": "^3.0.6",
|
||||||
"tsd": "^0.29.0"
|
"tsd": "^0.31.1"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
"libvips": ">=8.15.0"
|
},
|
||||||
|
"config": {
|
||||||
|
"libvips": ">=8.15.3"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
@@ -203,6 +211,11 @@
|
|||||||
"build/include"
|
"build/include"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"nyc": {
|
||||||
|
"include": [
|
||||||
|
"lib"
|
||||||
|
]
|
||||||
|
},
|
||||||
"tsd": {
|
"tsd": {
|
||||||
"directory": "test/types/"
|
"directory": "test/types/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
|
'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
|
||||||
'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")',
|
'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")',
|
||||||
'sharp_libvips_version': '<!(node -p "require(\'../package.json\').optionalDependencies[\'@img/sharp-libvips-<(platform_and_arch)\']")',
|
'sharp_libvips_version': '<!(node -p "require(\'../package.json\').optionalDependencies[\'@img/sharp-libvips-<(platform_and_arch)\']")',
|
||||||
|
'sharp_libvips_yarn_locator': '<!(node -p "require(\'../lib/libvips\').yarnLocator()")',
|
||||||
'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")',
|
'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")',
|
||||||
'sharp_libvips_cplusplus_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsCPlusPlusDir()")',
|
'sharp_libvips_cplusplus_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsCPlusPlusDir()")',
|
||||||
'sharp_libvips_lib_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsLibDir()")'
|
'sharp_libvips_lib_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsLibDir()")'
|
||||||
@@ -155,9 +156,10 @@
|
|||||||
'OTHER_LDFLAGS': [
|
'OTHER_LDFLAGS': [
|
||||||
# Ensure runtime linking is relative to sharp.node
|
# Ensure runtime linking is relative to sharp.node
|
||||||
'-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
'-Wl,-rpath,\'@loader_path/../../../<(sharp_libvips_version)/sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
|
||||||
'-Wl,-rpath,\'@loader_path/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath,\'@loader_path/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
'-Wl,-rpath,\'@loader_path/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
'-Wl,-rpath,\'@loader_path/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath,\'@loader_path/../../../../../@img-sharp-libvips-<(platform_and_arch)-npm-<(sharp_libvips_version)-<(sharp_libvips_yarn_locator)/node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@@ -170,15 +172,36 @@
|
|||||||
'-l:libvips-cpp.so.42'
|
'-l:libvips-cpp.so.42'
|
||||||
],
|
],
|
||||||
'ldflags': [
|
'ldflags': [
|
||||||
# Ensure runtime linking is relative to sharp.node
|
|
||||||
'-Wl,-s',
|
'-Wl,-s',
|
||||||
'-Wl,--disable-new-dtags',
|
'-Wl,--disable-new-dtags',
|
||||||
|
'-Wl,-z,nodelete',
|
||||||
'-Wl,-rpath=\'$$ORIGIN/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath=\'$$ORIGIN/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
'-Wl,-rpath=\'$$ORIGIN/../../../<(sharp_libvips_version)/sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath=\'$$ORIGIN/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
|
||||||
'-Wl,-rpath=\'$$ORIGIN/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
'-Wl,-rpath=\'$$ORIGIN/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
'-Wl,-rpath=\'$$ORIGIN/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
'-Wl,-rpath=\'$$ORIGIN/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||||
|
'-Wl,-rpath,\'$$ORIGIN/../../../../../@img-sharp-libvips-<(platform_and_arch)-npm-<(sharp_libvips_version)-<(sharp_libvips_yarn_locator)/node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}],
|
||||||
|
['OS == "emscripten"', {
|
||||||
|
'product_extension': 'node.js',
|
||||||
|
'link_settings': {
|
||||||
|
'ldflags': [
|
||||||
|
'-fexceptions',
|
||||||
|
'--pre-js=<!(node -p "require.resolve(\'./emscripten/pre.js\')")',
|
||||||
|
'-Oz',
|
||||||
|
'-sALLOW_MEMORY_GROWTH',
|
||||||
|
'-sENVIRONMENT=node',
|
||||||
|
'-sEXPORTED_FUNCTIONS=["emnapiInit", "_vips_shutdown", "_uv_library_shutdown"]',
|
||||||
|
'-sNODERAWFS',
|
||||||
|
'-sTEXTDECODER=0',
|
||||||
|
'-sWASM_ASYNC_COMPILATION=0',
|
||||||
|
'-sWASM_BIGINT'
|
||||||
|
],
|
||||||
|
'libraries': [
|
||||||
|
'<!@(PKG_CONFIG_PATH="<!(node -p "require(\'@img/sharp-libvips-dev-wasm32/lib\')")/pkgconfig" pkg-config --static --libs vips-cpp)'
|
||||||
|
],
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
@@ -203,11 +226,6 @@
|
|||||||
'configurations': {
|
'configurations': {
|
||||||
'Release': {
|
'Release': {
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['OS == "linux"', {
|
|
||||||
'cflags_cc': [
|
|
||||||
'-Wno-cast-function-type'
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
['target_arch == "arm"', {
|
['target_arch == "arm"', {
|
||||||
'cflags_cc': [
|
'cflags_cc': [
|
||||||
'-Wno-psabi'
|
'-Wno-psabi'
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace sharp {
|
|||||||
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
|
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
|
||||||
descriptor->bufferLength = buffer.Length();
|
descriptor->bufferLength = buffer.Length();
|
||||||
descriptor->buffer = buffer.Data();
|
descriptor->buffer = buffer.Data();
|
||||||
descriptor->isBuffer = TRUE;
|
descriptor->isBuffer = true;
|
||||||
}
|
}
|
||||||
descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
|
descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
|
||||||
// Density for vector-based input
|
// Density for vector-based input
|
||||||
@@ -384,7 +384,7 @@ namespace sharp {
|
|||||||
->set("access", descriptor->access)
|
->set("access", descriptor->access)
|
||||||
->set("fail_on", descriptor->failOn);
|
->set("fail_on", descriptor->failOn);
|
||||||
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
||||||
option->set("unlimited", TRUE);
|
option->set("unlimited", true);
|
||||||
}
|
}
|
||||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||||
option->set("dpi", descriptor->density);
|
option->set("dpi", descriptor->density);
|
||||||
@@ -488,7 +488,7 @@ namespace sharp {
|
|||||||
->set("access", descriptor->access)
|
->set("access", descriptor->access)
|
||||||
->set("fail_on", descriptor->failOn);
|
->set("fail_on", descriptor->failOn);
|
||||||
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
|
||||||
option->set("unlimited", TRUE);
|
option->set("unlimited", true);
|
||||||
}
|
}
|
||||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
|
||||||
option->set("dpi", descriptor->density);
|
option->set("dpi", descriptor->density);
|
||||||
@@ -531,7 +531,33 @@ namespace sharp {
|
|||||||
Does this image have an embedded profile?
|
Does this image have an embedded profile?
|
||||||
*/
|
*/
|
||||||
bool HasProfile(VImage image) {
|
bool HasProfile(VImage image) {
|
||||||
return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
|
return image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get copy of embedded profile.
|
||||||
|
*/
|
||||||
|
std::pair<char*, size_t> GetProfile(VImage image) {
|
||||||
|
std::pair<char*, size_t> icc(nullptr, 0);
|
||||||
|
if (HasProfile(image)) {
|
||||||
|
size_t length;
|
||||||
|
const void *data = image.get_blob(VIPS_META_ICC_NAME, &length);
|
||||||
|
icc.first = static_cast<char*>(g_malloc(length));
|
||||||
|
icc.second = length;
|
||||||
|
memcpy(icc.first, data, length);
|
||||||
|
}
|
||||||
|
return icc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set embedded profile.
|
||||||
|
*/
|
||||||
|
VImage SetProfile(VImage image, std::pair<char*, size_t> icc) {
|
||||||
|
if (icc.first != nullptr) {
|
||||||
|
image = image.copy();
|
||||||
|
image.set(VIPS_META_ICC_NAME, reinterpret_cast<VipsCallbackFn>(vips_area_free_cb), icc.first, icc.second);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -542,6 +568,27 @@ namespace sharp {
|
|||||||
return image.has_alpha();
|
return image.has_alpha();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
|
||||||
|
std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
|
||||||
|
std::string fieldName(field);
|
||||||
|
if (fieldName.substr(0, 8) == ("exif-ifd")) {
|
||||||
|
fieldNames->push_back(fieldName);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove all EXIF-related image fields.
|
||||||
|
*/
|
||||||
|
VImage RemoveExif(VImage image) {
|
||||||
|
std::vector<std::string> fieldNames;
|
||||||
|
vips_image_map(image.get_image(), static_cast<VipsImageMapFn>(RemoveExifCallback), &fieldNames);
|
||||||
|
for (const auto& f : fieldNames) {
|
||||||
|
image.remove(f.data());
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get EXIF Orientation of image, if any.
|
Get EXIF Orientation of image, if any.
|
||||||
*/
|
*/
|
||||||
@@ -721,7 +768,7 @@ namespace sharp {
|
|||||||
int *timeout = VIPS_NEW(im, int);
|
int *timeout = VIPS_NEW(im, int);
|
||||||
*timeout = seconds;
|
*timeout = seconds;
|
||||||
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
|
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
|
||||||
vips_image_set_progress(im, TRUE);
|
vips_image_set_progress(im, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,7 +778,7 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
|
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
|
||||||
if (*timeout > 0 && progress->run >= *timeout) {
|
if (*timeout > 0 && progress->run >= *timeout) {
|
||||||
vips_image_set_kill(im, TRUE);
|
vips_image_set_kill(im, true);
|
||||||
vips_error("timeout", "%d%% complete", progress->percent);
|
vips_error("timeout", "%d%% complete", progress->percent);
|
||||||
*timeout = 0;
|
*timeout = 0;
|
||||||
}
|
}
|
||||||
@@ -1034,9 +1081,10 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Ensure decoding remains sequential.
|
Ensure decoding remains sequential.
|
||||||
*/
|
*/
|
||||||
VImage StaySequential(VImage image, VipsAccess access, bool condition) {
|
VImage StaySequential(VImage image, bool condition) {
|
||||||
if (access == VIPS_ACCESS_SEQUENTIAL && condition) {
|
if (vips_image_is_sequential(image.get_image()) && condition) {
|
||||||
return image.copy_memory();
|
image = image.copy_memory().copy();
|
||||||
|
image.remove(VIPS_META_SEQUENTIAL);
|
||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/common.h
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
#if (VIPS_MAJOR_VERSION < 8) || \
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
||||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 15) || \
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 15) || \
|
||||||
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 0)
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 3)
|
||||||
#error "libvips version 8.15.0+ is required - please see https://sharp.pixelplumbing.com/install"
|
#error "libvips version 8.15.3+ is required - please see https://sharp.pixelplumbing.com/install"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||||
@@ -79,12 +79,12 @@ namespace sharp {
|
|||||||
buffer(nullptr),
|
buffer(nullptr),
|
||||||
failOn(VIPS_FAIL_ON_WARNING),
|
failOn(VIPS_FAIL_ON_WARNING),
|
||||||
limitInputPixels(0x3FFF * 0x3FFF),
|
limitInputPixels(0x3FFF * 0x3FFF),
|
||||||
unlimited(FALSE),
|
unlimited(false),
|
||||||
access(VIPS_ACCESS_RANDOM),
|
access(VIPS_ACCESS_RANDOM),
|
||||||
bufferLength(0),
|
bufferLength(0),
|
||||||
isBuffer(FALSE),
|
isBuffer(false),
|
||||||
density(72.0),
|
density(72.0),
|
||||||
ignoreIcc(FALSE),
|
ignoreIcc(false),
|
||||||
rawDepth(VIPS_FORMAT_UCHAR),
|
rawDepth(VIPS_FORMAT_UCHAR),
|
||||||
rawChannels(0),
|
rawChannels(0),
|
||||||
rawWidth(0),
|
rawWidth(0),
|
||||||
@@ -103,9 +103,9 @@ namespace sharp {
|
|||||||
textWidth(0),
|
textWidth(0),
|
||||||
textHeight(0),
|
textHeight(0),
|
||||||
textAlign(VIPS_ALIGN_LOW),
|
textAlign(VIPS_ALIGN_LOW),
|
||||||
textJustify(FALSE),
|
textJustify(false),
|
||||||
textDpi(72),
|
textDpi(72),
|
||||||
textRgba(FALSE),
|
textRgba(false),
|
||||||
textSpacing(0),
|
textSpacing(0),
|
||||||
textWrap(VIPS_TEXT_WRAP_WORD),
|
textWrap(VIPS_TEXT_WRAP_WORD),
|
||||||
textAutofitDpi(0) {}
|
textAutofitDpi(0) {}
|
||||||
@@ -222,12 +222,27 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
bool HasProfile(VImage image);
|
bool HasProfile(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get copy of embedded profile.
|
||||||
|
*/
|
||||||
|
std::pair<char*, size_t> GetProfile(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set embedded profile.
|
||||||
|
*/
|
||||||
|
VImage SetProfile(VImage image, std::pair<char*, size_t> icc);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have an alpha channel?
|
Does this image have an alpha channel?
|
||||||
Uses colour space interpretation with number of channels to guess this.
|
Uses colour space interpretation with number of channels to guess this.
|
||||||
*/
|
*/
|
||||||
bool HasAlpha(VImage image);
|
bool HasAlpha(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove all EXIF-related image fields.
|
||||||
|
*/
|
||||||
|
VImage RemoveExif(VImage image);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get EXIF Orientation of image, if any.
|
Get EXIF Orientation of image, if any.
|
||||||
*/
|
*/
|
||||||
@@ -371,7 +386,7 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Ensure decoding remains sequential.
|
Ensure decoding remains sequential.
|
||||||
*/
|
*/
|
||||||
VImage StaySequential(VImage image, VipsAccess access, bool condition = TRUE);
|
VImage StaySequential(VImage image, bool condition = true);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
|
|||||||
40
src/emscripten/common.gypi
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2013 Lovell Fuller and others.
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
{
|
||||||
|
'variables': {
|
||||||
|
'OS': 'emscripten'
|
||||||
|
},
|
||||||
|
'target_defaults': {
|
||||||
|
'default_configuration': 'Release',
|
||||||
|
'type': 'executable',
|
||||||
|
'cflags': [
|
||||||
|
'-pthread'
|
||||||
|
],
|
||||||
|
'cflags_cc': [
|
||||||
|
'-pthread'
|
||||||
|
],
|
||||||
|
'ldflags': [
|
||||||
|
'--js-library=<!(node -p "require(\'emnapi\').js_library")',
|
||||||
|
'-sAUTO_JS_LIBRARIES=0',
|
||||||
|
'-sAUTO_NATIVE_LIBRARIES=0',
|
||||||
|
'-sDEFAULT_TO_CXX=0',
|
||||||
|
'-sNODEJS_CATCH_EXIT=0',
|
||||||
|
'-sNODEJS_CATCH_REJECTION=0'
|
||||||
|
],
|
||||||
|
'defines': [
|
||||||
|
'__STDC_FORMAT_MACROS',
|
||||||
|
'BUILDING_NODE_EXTENSION',
|
||||||
|
'EMNAPI_WORKER_POOL_SIZE=1'
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'<!(node -p "require(\'emnapi\').include_dir")'
|
||||||
|
],
|
||||||
|
'sources': [
|
||||||
|
'<!@(node -p "require(\'emnapi\').sources.map(x => JSON.stringify(path.relative(process.cwd(), x))).join(\' \')")'
|
||||||
|
],
|
||||||
|
'configurations': {
|
||||||
|
'Release': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/emscripten/pre.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2013 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
/* global Module, ENV, _vips_shutdown, _uv_library_shutdown */
|
||||||
|
|
||||||
|
Module.preRun = () => {
|
||||||
|
ENV.VIPS_CONCURRENCY = Number(process.env.VIPS_CONCURRENCY) || 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Module.onRuntimeInitialized = () => {
|
||||||
|
module.exports = Module.emnapiInit({
|
||||||
|
context: require('@emnapi/runtime').getDefaultContext()
|
||||||
|
});
|
||||||
|
|
||||||
|
process.once('exit', () => {
|
||||||
|
_vips_shutdown();
|
||||||
|
_uv_library_shutdown();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
|
|
||||||
|
static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p);
|
||||||
|
|
||||||
class MetadataWorker : public Napi::AsyncWorker {
|
class MetadataWorker : public Napi::AsyncWorker {
|
||||||
public:
|
public:
|
||||||
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
|
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
|
||||||
@@ -131,6 +133,8 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|||||||
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
|
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
|
||||||
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
|
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
|
||||||
}
|
}
|
||||||
|
// PNG comments
|
||||||
|
vips_image_map(image.get_image(), readPNGComment, &baton->comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
@@ -246,6 +250,17 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|||||||
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
|
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
|
||||||
baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
||||||
}
|
}
|
||||||
|
if (baton->comments.size() > 0) {
|
||||||
|
int i = 0;
|
||||||
|
Napi::Array comments = Napi::Array::New(env, baton->comments.size());
|
||||||
|
for (auto &c : baton->comments) {
|
||||||
|
Napi::Object comment = Napi::Object::New(env);
|
||||||
|
comment.Set("keyword", c.first);
|
||||||
|
comment.Set("text", c.second);
|
||||||
|
comments.Set(i++, comment);
|
||||||
|
}
|
||||||
|
info.Set("comments", comments);
|
||||||
|
}
|
||||||
Callback().Call(Receiver().Value(), { env.Null(), info });
|
Callback().Call(Receiver().Value(), { env.Null(), info });
|
||||||
} else {
|
} else {
|
||||||
Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
||||||
@@ -285,3 +300,21 @@ Napi::Value metadata(const Napi::CallbackInfo& info) {
|
|||||||
|
|
||||||
return info.Env().Undefined();
|
return info.Env().Undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *PNG_COMMENT_START = "png-comment-";
|
||||||
|
const int PNG_COMMENT_START_LEN = strlen(PNG_COMMENT_START);
|
||||||
|
|
||||||
|
static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p) {
|
||||||
|
MetadataComments *comments = static_cast<MetadataComments *>(p);
|
||||||
|
|
||||||
|
if (vips_isprefix(PNG_COMMENT_START, field)) {
|
||||||
|
const char *keyword = strchr(field + PNG_COMMENT_START_LEN, '-');
|
||||||
|
const char *str;
|
||||||
|
if (keyword != NULL && !vips_image_get_string(image, field, &str)) {
|
||||||
|
keyword++; // Skip the hyphen
|
||||||
|
comments->push_back(std::make_pair(keyword, str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include "./common.h"
|
#include "./common.h"
|
||||||
|
|
||||||
|
typedef std::vector<std::pair<std::string, std::string>> MetadataComments;
|
||||||
|
|
||||||
struct MetadataBaton {
|
struct MetadataBaton {
|
||||||
// Input
|
// Input
|
||||||
sharp::InputDescriptor *input;
|
sharp::InputDescriptor *input;
|
||||||
@@ -47,6 +49,7 @@ struct MetadataBaton {
|
|||||||
size_t xmpLength;
|
size_t xmpLength;
|
||||||
char *tifftagPhotoshop;
|
char *tifftagPhotoshop;
|
||||||
size_t tifftagPhotoshopLength;
|
size_t tifftagPhotoshopLength;
|
||||||
|
MetadataComments comments;
|
||||||
std::string err;
|
std::string err;
|
||||||
|
|
||||||
MetadataBaton():
|
MetadataBaton():
|
||||||
|
|||||||
@@ -16,30 +16,44 @@ using vips::VError;
|
|||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the provided RGB.
|
||||||
*/
|
*/
|
||||||
VImage Tint(VImage image, double const a, double const b) {
|
VImage Tint(VImage image, std::vector<double> const tint) {
|
||||||
// Get original colourspace
|
std::vector<double> const tintLab = (VImage::black(1, 1) + tint)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB))
|
||||||
|
.getpoint(0, 0);
|
||||||
|
// LAB identity function
|
||||||
|
VImage identityLab = VImage::identity(VImage::option()->set("bands", 3))
|
||||||
|
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||||
|
// Scale luminance range, 0.0 to 1.0
|
||||||
|
VImage l = identityLab[0] / 100;
|
||||||
|
// Weighting functions
|
||||||
|
VImage weightL = 1.0 - 4.0 * ((l - 0.5) * (l - 0.5));
|
||||||
|
VImage weightAB = (weightL * tintLab).extract_band(1, VImage::option()->set("n", 2));
|
||||||
|
identityLab = identityLab[0].bandjoin(weightAB);
|
||||||
|
// Convert lookup table to sRGB
|
||||||
|
VImage lut = identityLab.colourspace(VIPS_INTERPRETATION_sRGB,
|
||||||
|
VImage::option()->set("source_space", VIPS_INTERPRETATION_LAB));
|
||||||
|
// Original colourspace
|
||||||
VipsInterpretation typeBeforeTint = image.interpretation();
|
VipsInterpretation typeBeforeTint = image.interpretation();
|
||||||
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
||||||
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
||||||
}
|
}
|
||||||
// Extract luminance
|
// Apply lookup table
|
||||||
VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
|
||||||
// Create the tinted version by combining the L from the original and the chroma from the tint
|
|
||||||
std::vector<double> chroma {a, b};
|
|
||||||
VImage tinted = luminance
|
|
||||||
.bandjoin(chroma)
|
|
||||||
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
|
|
||||||
.colourspace(typeBeforeTint);
|
|
||||||
// Attach original alpha channel, if any
|
|
||||||
if (HasAlpha(image)) {
|
if (HasAlpha(image)) {
|
||||||
// Extract original alpha channel
|
|
||||||
VImage alpha = image[image.bands() - 1];
|
VImage alpha = image[image.bands() - 1];
|
||||||
// Join alpha channel to normalised image
|
image = RemoveAlpha(image)
|
||||||
tinted = tinted.bandjoin(alpha);
|
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||||
|
.maplut(lut)
|
||||||
|
.colourspace(typeBeforeTint)
|
||||||
|
.bandjoin(alpha);
|
||||||
|
} else {
|
||||||
|
image = image
|
||||||
|
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||||
|
.maplut(lut)
|
||||||
|
.colourspace(typeBeforeTint);
|
||||||
}
|
}
|
||||||
return tinted;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -130,7 +144,7 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
||||||
*/
|
*/
|
||||||
VImage Blur(VImage image, double const sigma) {
|
VImage Blur(VImage image, double const sigma, VipsPrecision precision, double const minAmpl) {
|
||||||
if (sigma == -1.0) {
|
if (sigma == -1.0) {
|
||||||
// Fast, mild blur - averages neighbouring pixels
|
// Fast, mild blur - averages neighbouring pixels
|
||||||
VImage blur = VImage::new_matrixv(3, 3,
|
VImage blur = VImage::new_matrixv(3, 3,
|
||||||
@@ -141,7 +155,9 @@ namespace sharp {
|
|||||||
return image.conv(blur);
|
return image.conv(blur);
|
||||||
} else {
|
} else {
|
||||||
// Slower, accurate Gaussian blur
|
// Slower, accurate Gaussian blur
|
||||||
return StaySequential(image, VIPS_ACCESS_SEQUENTIAL).gaussblur(sigma);
|
return StaySequential(image).gaussblur(sigma, VImage::option()
|
||||||
|
->set("precision", precision)
|
||||||
|
->set("min_ampl", minAmpl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,10 +166,10 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage Convolve(VImage image, int const width, int const height,
|
VImage Convolve(VImage image, int const width, int const height,
|
||||||
double const scale, double const offset,
|
double const scale, double const offset,
|
||||||
std::unique_ptr<double[]> const &kernel_v
|
std::vector<double> const &kernel_v
|
||||||
) {
|
) {
|
||||||
VImage kernel = VImage::new_from_memory(
|
VImage kernel = VImage::new_from_memory(
|
||||||
kernel_v.get(),
|
static_cast<void*>(const_cast<double*>(kernel_v.data())),
|
||||||
width * height * sizeof(double),
|
width * height * sizeof(double),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -169,19 +185,21 @@ namespace sharp {
|
|||||||
* Recomb with a Matrix of the given bands/channel size.
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
* Eg. RGB will be a 3x3 matrix.
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
*/
|
*/
|
||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
|
VImage Recomb(VImage image, std::vector<double> const& matrix) {
|
||||||
double *m = matrix.get();
|
double* m = const_cast<double*>(matrix.data());
|
||||||
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
return image
|
if (matrix.size() == 9) {
|
||||||
.recomb(image.bands() == 3
|
return image
|
||||||
? VImage::new_from_memory(
|
.recomb(image.bands() == 3
|
||||||
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
|
? VImage::new_matrix(3, 3, m, 9)
|
||||||
)
|
: VImage::new_matrixv(4, 4,
|
||||||
: VImage::new_matrixv(4, 4,
|
m[0], m[1], m[2], 0.0,
|
||||||
m[0], m[1], m[2], 0.0,
|
m[3], m[4], m[5], 0.0,
|
||||||
m[3], m[4], m[5], 0.0,
|
m[6], m[7], m[8], 0.0,
|
||||||
m[6], m[7], m[8], 0.0,
|
0.0, 0.0, 0.0, 1.0));
|
||||||
0.0, 0.0, 0.0, 1.0));
|
} else {
|
||||||
|
return image.recomb(VImage::new_matrix(4, 4, m, 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VImage Modulate(VImage image, double const brightness, double const saturation,
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
@@ -372,7 +390,7 @@ namespace sharp {
|
|||||||
pages.reserve(nPages);
|
pages.reserve(nPages);
|
||||||
|
|
||||||
// Split the image into cropped frames
|
// Split the image into cropped frames
|
||||||
image = StaySequential(image, VIPS_ACCESS_SEQUENTIAL);
|
image = StaySequential(image);
|
||||||
for (int i = 0; i < nPages; i++) {
|
for (int i = 0; i < nPages; i++) {
|
||||||
pages.push_back(
|
pages.push_back(
|
||||||
image.extract_area(left, *pageHeight * i + top, width, height));
|
image.extract_area(left, *pageHeight * i + top, width, height));
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ using vips::VImage;
|
|||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the provided RGB.
|
||||||
*/
|
*/
|
||||||
VImage Tint(VImage image, double const a, double const b);
|
VImage Tint(VImage image, std::vector<double> const tint);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stretch luminance to cover full dynamic range.
|
* Stretch luminance to cover full dynamic range.
|
||||||
@@ -47,13 +47,13 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
||||||
*/
|
*/
|
||||||
VImage Blur(VImage image, double const sigma);
|
VImage Blur(VImage image, double const sigma, VipsPrecision precision, double const minAmpl);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convolution with a kernel.
|
* Convolution with a kernel.
|
||||||
*/
|
*/
|
||||||
VImage Convolve(VImage image, int const width, int const height,
|
VImage Convolve(VImage image, int const width, int const height,
|
||||||
double const scale, double const offset, std::unique_ptr<double[]> const &kernel_v);
|
double const scale, double const offset, std::vector<double> const &kernel_v);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
|
||||||
@@ -95,7 +95,7 @@ namespace sharp {
|
|||||||
* Recomb with a Matrix of the given bands/channel size.
|
* Recomb with a Matrix of the given bands/channel size.
|
||||||
* Eg. RGB will be a 3x3 matrix.
|
* Eg. RGB will be a 3x3 matrix.
|
||||||
*/
|
*/
|
||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
VImage Recomb(VImage image, std::vector<double> const &matrix);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Modulate brightness, saturation, hue and lightness
|
* Modulate brightness, saturation, hue and lightness
|
||||||
|
|||||||
241
src/pipeline.cc
@@ -54,7 +54,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::ImageType inputImageType;
|
sharp::ImageType inputImageType;
|
||||||
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
||||||
VipsAccess access = baton->input->access;
|
VipsAccess access = baton->input->access;
|
||||||
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
|
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
|
||||||
|
|
||||||
int nPages = baton->input->pages;
|
int nPages = baton->input->pages;
|
||||||
if (nPages == -1) {
|
if (nPages == -1) {
|
||||||
@@ -70,8 +70,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Calculate angle of rotation
|
// Calculate angle of rotation
|
||||||
VipsAngle rotation = VIPS_ANGLE_D0;
|
VipsAngle rotation = VIPS_ANGLE_D0;
|
||||||
VipsAngle autoRotation = VIPS_ANGLE_D0;
|
VipsAngle autoRotation = VIPS_ANGLE_D0;
|
||||||
bool autoFlip = FALSE;
|
bool autoFlip = false;
|
||||||
bool autoFlop = FALSE;
|
bool autoFlop = false;
|
||||||
|
|
||||||
if (baton->useExifOrientation) {
|
if (baton->useExifOrientation) {
|
||||||
// Rotate and flip image according to Exif orientation
|
// Rotate and flip image according to Exif orientation
|
||||||
@@ -88,7 +88,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
baton->rotationAngle != 0.0);
|
baton->rotationAngle != 0.0);
|
||||||
|
|
||||||
if (shouldRotateBefore) {
|
if (shouldRotateBefore) {
|
||||||
image = sharp::StaySequential(image, access,
|
image = sharp::StaySequential(image,
|
||||||
rotation != VIPS_ANGLE_D0 ||
|
rotation != VIPS_ANGLE_D0 ||
|
||||||
autoRotation != VIPS_ANGLE_D0 ||
|
autoRotation != VIPS_ANGLE_D0 ||
|
||||||
autoFlip ||
|
autoFlip ||
|
||||||
@@ -96,31 +96,37 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
baton->rotationAngle != 0.0);
|
baton->rotationAngle != 0.0);
|
||||||
|
|
||||||
if (autoRotation != VIPS_ANGLE_D0) {
|
if (autoRotation != VIPS_ANGLE_D0) {
|
||||||
|
if (autoRotation != VIPS_ANGLE_D180) {
|
||||||
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
|
}
|
||||||
image = image.rot(autoRotation);
|
image = image.rot(autoRotation);
|
||||||
autoRotation = VIPS_ANGLE_D0;
|
autoRotation = VIPS_ANGLE_D0;
|
||||||
}
|
}
|
||||||
if (autoFlip) {
|
if (autoFlip) {
|
||||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||||
autoFlip = FALSE;
|
autoFlip = false;
|
||||||
} else if (baton->flip) {
|
} else if (baton->flip) {
|
||||||
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
||||||
baton->flip = FALSE;
|
baton->flip = false;
|
||||||
}
|
}
|
||||||
if (autoFlop) {
|
if (autoFlop) {
|
||||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||||
autoFlop = FALSE;
|
autoFlop = false;
|
||||||
} else if (baton->flop) {
|
} else if (baton->flop) {
|
||||||
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||||
baton->flop = FALSE;
|
baton->flop = false;
|
||||||
}
|
}
|
||||||
if (rotation != VIPS_ANGLE_D0) {
|
if (rotation != VIPS_ANGLE_D0) {
|
||||||
|
if (rotation != VIPS_ANGLE_D180) {
|
||||||
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
|
}
|
||||||
image = image.rot(rotation);
|
image = image.rot(rotation);
|
||||||
rotation = VIPS_ANGLE_D0;
|
rotation = VIPS_ANGLE_D0;
|
||||||
}
|
}
|
||||||
if (baton->rotationAngle != 0.0) {
|
if (baton->rotationAngle != 0.0) {
|
||||||
MultiPageUnsupported(nPages, "Rotate");
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +134,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Trim
|
// Trim
|
||||||
if (baton->trimThreshold >= 0.0) {
|
if (baton->trimThreshold >= 0.0) {
|
||||||
MultiPageUnsupported(nPages, "Trim");
|
MultiPageUnsupported(nPages, "Trim");
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
|
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
|
||||||
baton->trimOffsetLeft = image.xoffset();
|
baton->trimOffsetLeft = image.xoffset();
|
||||||
baton->trimOffsetTop = image.yoffset();
|
baton->trimOffsetTop = image.yoffset();
|
||||||
@@ -183,7 +189,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// - input colourspace is not specified;
|
// - input colourspace is not specified;
|
||||||
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
||||||
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
|
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
|
||||||
baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
|
baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
|
||||||
|
|
||||||
if (shouldPreShrink) {
|
if (shouldPreShrink) {
|
||||||
// The common part of the shrink: the bit by which both axes must be shrunk
|
// The common part of the shrink: the bit by which both axes must be shrunk
|
||||||
@@ -315,17 +321,24 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we're using a device-independent colour space
|
// Ensure we're using a device-independent colour space
|
||||||
|
std::pair<char*, size_t> inputProfile(nullptr, 0);
|
||||||
|
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
|
||||||
|
// Cache input profile for use with output
|
||||||
|
inputProfile = sharp::GetProfile(image);
|
||||||
|
baton->input->ignoreIcc = true;
|
||||||
|
}
|
||||||
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
||||||
if (
|
if (
|
||||||
sharp::HasProfile(image) &&
|
sharp::HasProfile(image) &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
||||||
|
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
|
||||||
!baton->input->ignoreIcc
|
!baton->input->ignoreIcc
|
||||||
) {
|
) {
|
||||||
// Convert to sRGB/P3 using embedded profile
|
// Convert to sRGB/P3 using embedded profile
|
||||||
try {
|
try {
|
||||||
image = image.icc_transform(processingProfile, VImage::option()
|
image = image.icc_transform(processingProfile, VImage::option()
|
||||||
->set("embedded", TRUE)
|
->set("embedded", true)
|
||||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
@@ -333,7 +346,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
image.interpretation() == VIPS_INTERPRETATION_CMYK &&
|
image.interpretation() == VIPS_INTERPRETATION_CMYK &&
|
||||||
baton->colourspaceInput != VIPS_INTERPRETATION_CMYK
|
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK
|
||||||
) {
|
) {
|
||||||
image = image.icc_transform(processingProfile, VImage::option()
|
image = image.icc_transform(processingProfile, VImage::option()
|
||||||
->set("input_profile", "cmyk")
|
->set("input_profile", "cmyk")
|
||||||
@@ -345,11 +358,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
image = sharp::Flatten(image, baton->flattenBackground);
|
image = sharp::Flatten(image, baton->flattenBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negate the colours in the image
|
|
||||||
if (baton->negate) {
|
|
||||||
image = sharp::Negate(image, baton->negateAlpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gamma encoding (darken)
|
// Gamma encoding (darken)
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||||
image = sharp::Gamma(image, 1.0 / baton->gamma);
|
image = sharp::Gamma(image, 1.0 / baton->gamma);
|
||||||
@@ -385,13 +393,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("kernel", baton->kernel));
|
->set("kernel", baton->kernel));
|
||||||
}
|
}
|
||||||
|
|
||||||
image = sharp::StaySequential(image, access,
|
image = sharp::StaySequential(image,
|
||||||
autoRotation != VIPS_ANGLE_D0 ||
|
autoRotation != VIPS_ANGLE_D0 ||
|
||||||
baton->flip ||
|
baton->flip ||
|
||||||
autoFlip ||
|
autoFlip ||
|
||||||
rotation != VIPS_ANGLE_D0);
|
rotation != VIPS_ANGLE_D0);
|
||||||
// Auto-rotate post-extract
|
// Auto-rotate post-extract
|
||||||
if (autoRotation != VIPS_ANGLE_D0) {
|
if (autoRotation != VIPS_ANGLE_D0) {
|
||||||
|
if (autoRotation != VIPS_ANGLE_D180) {
|
||||||
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
|
}
|
||||||
image = image.rot(autoRotation);
|
image = image.rot(autoRotation);
|
||||||
}
|
}
|
||||||
// Mirror vertically (up-down) about the x-axis
|
// Mirror vertically (up-down) about the x-axis
|
||||||
@@ -404,6 +415,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
// Rotate post-extract 90-angle
|
// Rotate post-extract 90-angle
|
||||||
if (rotation != VIPS_ANGLE_D0) {
|
if (rotation != VIPS_ANGLE_D0) {
|
||||||
|
if (rotation != VIPS_ANGLE_D180) {
|
||||||
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
|
}
|
||||||
image = image.rot(rotation);
|
image = image.rot(rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +429,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
|
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
|
||||||
baton->joinChannelIn[i]->access = access;
|
baton->joinChannelIn[i]->access = access;
|
||||||
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
|
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
|
||||||
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
|
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline);
|
||||||
image = image.bandjoin(joinImage);
|
image = image.bandjoin(joinImage);
|
||||||
}
|
}
|
||||||
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
||||||
@@ -482,7 +496,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
// Attention-based or Entropy-based crop
|
// Attention-based or Entropy-based crop
|
||||||
MultiPageUnsupported(nPages, "Resize strategy");
|
MultiPageUnsupported(nPages, "Resize strategy");
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
||||||
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
|
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
|
||||||
->set("premultiplied", shouldPremultiplyAlpha)
|
->set("premultiplied", shouldPremultiplyAlpha)
|
||||||
@@ -501,7 +515,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Rotate post-extract non-90 angle
|
// Rotate post-extract non-90 angle
|
||||||
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
||||||
MultiPageUnsupported(nPages, "Rotate");
|
MultiPageUnsupported(nPages, "Rotate");
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||||
@@ -525,7 +539,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Affine transform
|
// Affine transform
|
||||||
if (!baton->affineMatrix.empty()) {
|
if (!baton->affineMatrix.empty()) {
|
||||||
MultiPageUnsupported(nPages, "Affine");
|
MultiPageUnsupported(nPages, "Affine");
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
||||||
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
|
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
|
||||||
@@ -548,6 +562,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
||||||
|
|
||||||
|
image = sharp::StaySequential(image, nPages > 1);
|
||||||
image = nPages > 1
|
image = nPages > 1
|
||||||
? sharp::EmbedMultiPage(image,
|
? sharp::EmbedMultiPage(image,
|
||||||
baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
||||||
@@ -556,6 +571,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
VImage::option()->set("extend", baton->extendWith)->set("background", background));
|
VImage::option()->set("extend", baton->extendWith)->set("background", background));
|
||||||
} else {
|
} else {
|
||||||
std::vector<double> ignoredBackground(1);
|
std::vector<double> ignoredBackground(1);
|
||||||
|
image = sharp::StaySequential(image);
|
||||||
image = nPages > 1
|
image = nPages > 1
|
||||||
? sharp::EmbedMultiPage(image,
|
? sharp::EmbedMultiPage(image,
|
||||||
baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
||||||
@@ -577,7 +593,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
// Blur
|
// Blur
|
||||||
if (shouldBlur) {
|
if (shouldBlur) {
|
||||||
image = sharp::Blur(image, baton->blurSigma);
|
image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unflatten the image
|
// Unflatten the image
|
||||||
@@ -594,7 +610,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recomb
|
// Recomb
|
||||||
if (baton->recombMatrix != NULL) {
|
if (!baton->recombMatrix.empty()) {
|
||||||
image = sharp::Recomb(image, baton->recombMatrix);
|
image = sharp::Recomb(image, baton->recombMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +640,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
||||||
composite->input->access = access;
|
composite->input->access = access;
|
||||||
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
||||||
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
|
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline);
|
||||||
// Verify within current dimensions
|
// Verify within current dimensions
|
||||||
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
||||||
throw vips::VError("Image to composite must have same dimensions or smaller");
|
throw vips::VError("Image to composite must have same dimensions or smaller");
|
||||||
@@ -651,7 +667,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (across != 0 || down != 0) {
|
if (across != 0 || down != 0) {
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
compositeImage = sharp::StaySequential(compositeImage, access).replicate(across, down);
|
compositeImage = sharp::StaySequential(compositeImage).replicate(across, down);
|
||||||
if (composite->hasOffset) {
|
if (composite->hasOffset) {
|
||||||
std::tie(left, top) = sharp::CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
||||||
@@ -709,13 +725,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
// Apply normalisation - stretch luminance to cover full dynamic range
|
// Apply normalisation - stretch luminance to cover full dynamic range
|
||||||
if (baton->normalise) {
|
if (baton->normalise) {
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
||||||
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
||||||
image = sharp::StaySequential(image, access);
|
image = sharp::StaySequential(image);
|
||||||
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +741,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
||||||
baton->boolean->access = access;
|
baton->boolean->access = access;
|
||||||
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
|
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
|
||||||
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
|
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline);
|
||||||
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
|
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
|
||||||
image = sharp::RemoveGifPalette(image);
|
image = sharp::RemoveGifPalette(image);
|
||||||
}
|
}
|
||||||
@@ -736,8 +752,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tint the image
|
// Tint the image
|
||||||
if (baton->tintA < 128.0 || baton->tintB < 128.0) {
|
if (baton->tint[0] >= 0.0) {
|
||||||
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
image = sharp::Tint(image, baton->tint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove alpha channel, if any
|
// Remove alpha channel, if any
|
||||||
@@ -758,9 +774,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
||||||
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
||||||
// Transform colours from embedded profile to output profile
|
// Transform colours from embedded profile to output profile
|
||||||
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
|
||||||
image = image.icc_transform("srgb", VImage::option()
|
baton->withIccProfile.empty() && sharp::HasProfile(image)) {
|
||||||
->set("embedded", TRUE)
|
image = image.icc_transform(processingProfile, VImage::option()
|
||||||
|
->set("embedded", true)
|
||||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
}
|
}
|
||||||
@@ -787,27 +804,40 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply output ICC profile
|
// Apply output ICC profile
|
||||||
if (baton->withMetadata) {
|
if (!baton->withIccProfile.empty()) {
|
||||||
image = image.icc_transform(
|
try {
|
||||||
baton->withMetadataIcc.empty() ? "srgb" : const_cast<char*>(baton->withMetadataIcc.data()),
|
image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
|
||||||
VImage::option()
|
|
||||||
->set("input_profile", processingProfile)
|
->set("input_profile", processingProfile)
|
||||||
->set("embedded", TRUE)
|
->set("embedded", true)
|
||||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
|
} catch(...) {
|
||||||
|
sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
|
||||||
|
}
|
||||||
|
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
|
||||||
|
image = sharp::SetProfile(image, inputProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Negate the colours in the image
|
||||||
|
if (baton->negate) {
|
||||||
|
image = sharp::Negate(image, baton->negateAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
// Override EXIF Orientation tag
|
// Override EXIF Orientation tag
|
||||||
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
if (baton->withMetadataOrientation != -1) {
|
||||||
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
||||||
}
|
}
|
||||||
// Override pixel density
|
// Override pixel density
|
||||||
if (baton->withMetadataDensity > 0) {
|
if (baton->withMetadataDensity > 0) {
|
||||||
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
||||||
}
|
}
|
||||||
// Metadata key/value pairs, e.g. EXIF
|
// EXIF key/value pairs
|
||||||
if (!baton->withMetadataStrs.empty()) {
|
if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {
|
||||||
image = image.copy();
|
image = image.copy();
|
||||||
for (const auto& s : baton->withMetadataStrs) {
|
if (!baton->withExifMerge) {
|
||||||
|
image = sharp::RemoveExif(image);
|
||||||
|
}
|
||||||
|
for (const auto& s : baton->withExif) {
|
||||||
image.set(s.first.data(), s.second.data());
|
image.set(s.first.data(), s.second.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -820,6 +850,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
image = sharp::SetAnimationProperties(
|
image = sharp::SetAnimationProperties(
|
||||||
image, nPages, targetPageHeight, baton->delay, baton->loop);
|
image, nPages, targetPageHeight, baton->delay, baton->loop);
|
||||||
|
|
||||||
|
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
||||||
|
baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
|
||||||
|
baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
|
||||||
|
}
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
sharp::SetTimeout(image, baton->timeoutSeconds);
|
sharp::SetTimeout(image, baton->timeoutSeconds);
|
||||||
if (baton->fileOut.empty()) {
|
if (baton->fileOut.empty()) {
|
||||||
@@ -828,7 +863,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write JPEG to buffer
|
// Write JPEG to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->jpegQuality)
|
->set("Q", baton->jpegQuality)
|
||||||
->set("interlace", baton->jpegProgressive)
|
->set("interlace", baton->jpegProgressive)
|
||||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||||
@@ -870,7 +905,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write PNG to buffer
|
// Write PNG to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("interlace", baton->pngProgressive)
|
->set("interlace", baton->pngProgressive)
|
||||||
->set("compression", baton->pngCompressionLevel)
|
->set("compression", baton->pngCompressionLevel)
|
||||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||||
@@ -889,7 +924,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write WEBP to buffer
|
// Write WEBP to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->webpQuality)
|
->set("Q", baton->webpQuality)
|
||||||
->set("lossless", baton->webpLossless)
|
->set("lossless", baton->webpLossless)
|
||||||
->set("near_lossless", baton->webpNearLossless)
|
->set("near_lossless", baton->webpNearLossless)
|
||||||
@@ -909,7 +944,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write GIF to buffer
|
// Write GIF to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("bitdepth", baton->gifBitdepth)
|
->set("bitdepth", baton->gifBitdepth)
|
||||||
->set("effort", baton->gifEffort)
|
->set("effort", baton->gifEffort)
|
||||||
->set("reuse", baton->gifReuse)
|
->set("reuse", baton->gifReuse)
|
||||||
@@ -934,7 +969,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||||
}
|
}
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->tiffQuality)
|
->set("Q", baton->tiffQuality)
|
||||||
->set("bitdepth", baton->tiffBitdepth)
|
->set("bitdepth", baton->tiffBitdepth)
|
||||||
->set("compression", baton->tiffCompression)
|
->set("compression", baton->tiffCompression)
|
||||||
@@ -958,11 +993,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("effort", baton->heifEffort)
|
->set("effort", baton->heifEffort)
|
||||||
->set("bitdepth", 8)
|
->set("bitdepth", baton->heifBitdepth)
|
||||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
->set("lossless", baton->heifLossless)));
|
->set("lossless", baton->heifLossless)));
|
||||||
@@ -977,7 +1012,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (!sharp::HasAlpha(image)) {
|
if (!sharp::HasAlpha(image)) {
|
||||||
baton->tileBackground.pop_back();
|
baton->tileBackground.pop_back();
|
||||||
}
|
}
|
||||||
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
||||||
vips::VOption *options = BuildOptionsDZ(baton);
|
vips::VOption *options = BuildOptionsDZ(baton);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
@@ -990,7 +1025,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write JXL to buffer
|
// Write JXL to buffer
|
||||||
image = sharp::RemoveAnimationProperties(image);
|
image = sharp::RemoveAnimationProperties(image);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("distance", baton->jxlDistance)
|
->set("distance", baton->jxlDistance)
|
||||||
->set("tier", baton->jxlDecodingTier)
|
->set("tier", baton->jxlDecodingTier)
|
||||||
->set("effort", baton->jxlEffort)
|
->set("effort", baton->jxlEffort)
|
||||||
@@ -1051,7 +1086,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write JPEG to file
|
// Write JPEG to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||||
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->jpegQuality)
|
->set("Q", baton->jpegQuality)
|
||||||
->set("interlace", baton->jpegProgressive)
|
->set("interlace", baton->jpegProgressive)
|
||||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||||
@@ -1081,7 +1116,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write PNG to file
|
// Write PNG to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||||
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("interlace", baton->pngProgressive)
|
->set("interlace", baton->pngProgressive)
|
||||||
->set("compression", baton->pngCompressionLevel)
|
->set("compression", baton->pngCompressionLevel)
|
||||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||||
@@ -1096,7 +1131,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write WEBP to file
|
// Write WEBP to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||||
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->webpQuality)
|
->set("Q", baton->webpQuality)
|
||||||
->set("lossless", baton->webpLossless)
|
->set("lossless", baton->webpLossless)
|
||||||
->set("near_lossless", baton->webpNearLossless)
|
->set("near_lossless", baton->webpNearLossless)
|
||||||
@@ -1112,7 +1147,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write GIF to file
|
// Write GIF to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("bitdepth", baton->gifBitdepth)
|
->set("bitdepth", baton->gifBitdepth)
|
||||||
->set("effort", baton->gifEffort)
|
->set("effort", baton->gifEffort)
|
||||||
->set("reuse", baton->gifReuse)
|
->set("reuse", baton->gifReuse)
|
||||||
@@ -1131,7 +1166,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||||
}
|
}
|
||||||
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->tiffQuality)
|
->set("Q", baton->tiffQuality)
|
||||||
->set("bitdepth", baton->tiffBitdepth)
|
->set("bitdepth", baton->tiffBitdepth)
|
||||||
->set("compression", baton->tiffCompression)
|
->set("compression", baton->tiffCompression)
|
||||||
@@ -1151,11 +1186,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("effort", baton->heifEffort)
|
->set("effort", baton->heifEffort)
|
||||||
->set("bitdepth", 8)
|
->set("bitdepth", baton->heifBitdepth)
|
||||||
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
->set("lossless", baton->heifLossless));
|
->set("lossless", baton->heifLossless));
|
||||||
@@ -1165,7 +1200,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Write JXL to file
|
// Write JXL to file
|
||||||
image = sharp::RemoveAnimationProperties(image);
|
image = sharp::RemoveAnimationProperties(image);
|
||||||
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("distance", baton->jxlDistance)
|
->set("distance", baton->jxlDistance)
|
||||||
->set("tier", baton->jxlDecodingTier)
|
->set("tier", baton->jxlDecodingTier)
|
||||||
->set("effort", baton->jxlEffort)
|
->set("effort", baton->jxlEffort)
|
||||||
@@ -1179,7 +1214,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (!sharp::HasAlpha(image)) {
|
if (!sharp::HasAlpha(image)) {
|
||||||
baton->tileBackground.pop_back();
|
baton->tileBackground.pop_back();
|
||||||
}
|
}
|
||||||
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
||||||
vips::VOption *options = BuildOptionsDZ(baton);
|
vips::VOption *options = BuildOptionsDZ(baton);
|
||||||
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
||||||
baton->formatOut = "dz";
|
baton->formatOut = "dz";
|
||||||
@@ -1187,7 +1222,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
|
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
|
||||||
// Write V to file
|
// Write V to file
|
||||||
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata));
|
->set("keep", baton->keepMetadata));
|
||||||
baton->formatOut = "v";
|
baton->formatOut = "v";
|
||||||
} else {
|
} else {
|
||||||
// Unsupported output format
|
// Unsupported output format
|
||||||
@@ -1255,6 +1290,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (baton->input->textAutofitDpi) {
|
if (baton->input->textAutofitDpi) {
|
||||||
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
|
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
|
||||||
}
|
}
|
||||||
|
if (baton->pageHeightOut) {
|
||||||
|
info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
|
||||||
|
info.Set("pages", static_cast<int32_t>(baton->pagesOut));
|
||||||
|
}
|
||||||
|
|
||||||
if (baton->bufferOutLength > 0) {
|
if (baton->bufferOutLength > 0) {
|
||||||
// Add buffer size to info
|
// Add buffer size to info
|
||||||
@@ -1311,16 +1350,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
std::tuple<VipsAngle, bool, bool>
|
std::tuple<VipsAngle, bool, bool>
|
||||||
CalculateExifRotationAndFlip(int const exifOrientation) {
|
CalculateExifRotationAndFlip(int const exifOrientation) {
|
||||||
VipsAngle rotate = VIPS_ANGLE_D0;
|
VipsAngle rotate = VIPS_ANGLE_D0;
|
||||||
bool flip = FALSE;
|
bool flip = false;
|
||||||
bool flop = FALSE;
|
bool flop = false;
|
||||||
switch (exifOrientation) {
|
switch (exifOrientation) {
|
||||||
case 6: rotate = VIPS_ANGLE_D90; break;
|
case 6: rotate = VIPS_ANGLE_D90; break;
|
||||||
case 3: rotate = VIPS_ANGLE_D180; break;
|
case 3: rotate = VIPS_ANGLE_D180; break;
|
||||||
case 8: rotate = VIPS_ANGLE_D270; break;
|
case 8: rotate = VIPS_ANGLE_D270; break;
|
||||||
case 2: flop = TRUE; break; // flop 1
|
case 2: flop = true; break; // flop 1
|
||||||
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
|
case 7: flip = true; rotate = VIPS_ANGLE_D90; break; // flip 6
|
||||||
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
|
case 4: flop = true; rotate = VIPS_ANGLE_D180; break; // flop 3
|
||||||
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
|
case 5: flip = true; rotate = VIPS_ANGLE_D270; break; // flip 8
|
||||||
}
|
}
|
||||||
return std::make_tuple(rotate, flip, flop);
|
return std::make_tuple(rotate, flip, flop);
|
||||||
}
|
}
|
||||||
@@ -1368,7 +1407,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
std::string suffix;
|
std::string suffix;
|
||||||
if (baton->tileFormat == "png") {
|
if (baton->tileFormat == "png") {
|
||||||
std::vector<std::pair<std::string, std::string>> options {
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
{"interlace", baton->pngProgressive ? "true" : "false"},
|
||||||
{"compression", std::to_string(baton->pngCompressionLevel)},
|
{"compression", std::to_string(baton->pngCompressionLevel)},
|
||||||
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
||||||
};
|
};
|
||||||
@@ -1377,31 +1416,31 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
std::vector<std::pair<std::string, std::string>> options {
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
{"Q", std::to_string(baton->webpQuality)},
|
{"Q", std::to_string(baton->webpQuality)},
|
||||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||||
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
{"lossless", baton->webpLossless ? "true" : "false"},
|
||||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
{"near_lossless", baton->webpNearLossless ? "true" : "false"},
|
||||||
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
{"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
|
||||||
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
||||||
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
|
{"min_size", baton->webpMinSize ? "true" : "false"},
|
||||||
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
|
{"mixed", baton->webpMixed ? "true" : "false"},
|
||||||
{"effort", std::to_string(baton->webpEffort)}
|
{"effort", std::to_string(baton->webpEffort)}
|
||||||
};
|
};
|
||||||
suffix = AssembleSuffixString(".webp", options);
|
suffix = AssembleSuffixString(".webp", options);
|
||||||
} else {
|
} else {
|
||||||
std::vector<std::pair<std::string, std::string>> options {
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
{"Q", std::to_string(baton->jpegQuality)},
|
{"Q", std::to_string(baton->jpegQuality)},
|
||||||
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
{"interlace", baton->jpegProgressive ? "true" : "false"},
|
||||||
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
||||||
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
{"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"},
|
||||||
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
||||||
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
{"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"},
|
||||||
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
{"optimize_scans", baton->jpegOptimiseScans ? "true": "false"},
|
||||||
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
|
{"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"}
|
||||||
};
|
};
|
||||||
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
||||||
suffix = AssembleSuffixString(extname, options);
|
suffix = AssembleSuffixString(extname, options);
|
||||||
}
|
}
|
||||||
vips::VOption *options = VImage::option()
|
vips::VOption *options = VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("keep", baton->keepMetadata)
|
||||||
->set("tile_size", baton->tileSize)
|
->set("tile_size", baton->tileSize)
|
||||||
->set("overlap", baton->tileOverlap)
|
->set("overlap", baton->tileOverlap)
|
||||||
->set("container", baton->tileContainer)
|
->set("container", baton->tileContainer)
|
||||||
@@ -1503,6 +1542,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->negate = sharp::AttrAsBool(options, "negate");
|
baton->negate = sharp::AttrAsBool(options, "negate");
|
||||||
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
|
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
|
||||||
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
||||||
|
baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION);
|
||||||
|
baton->minAmpl = sharp::AttrAsDouble(options, "minAmpl");
|
||||||
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
||||||
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
||||||
baton->hue = sharp::AttrAsInt32(options, "hue");
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
||||||
@@ -1527,8 +1568,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
||||||
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
||||||
baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
|
baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
|
||||||
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
baton->tint = sharp::AttrAsVectorOfDouble(options, "tint");
|
||||||
baton->tintB = sharp::AttrAsDouble(options, "tintB");
|
|
||||||
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
||||||
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
||||||
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
||||||
@@ -1569,23 +1609,24 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
|
baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
|
||||||
baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
|
baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
|
||||||
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
|
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
|
||||||
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
|
baton->convKernel.resize(kernelSize);
|
||||||
Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
|
Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
|
||||||
for (unsigned int i = 0; i < kernelSize; i++) {
|
for (unsigned int i = 0; i < kernelSize; i++) {
|
||||||
baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
|
baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.Has("recombMatrix")) {
|
if (options.Has("recombMatrix")) {
|
||||||
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
|
|
||||||
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
|
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
|
||||||
for (unsigned int i = 0; i < 9; i++) {
|
unsigned int matrixElements = recombMatrix.Length();
|
||||||
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
baton->recombMatrix.resize(matrixElements);
|
||||||
|
for (unsigned int i = 0; i < matrixElements; i++) {
|
||||||
|
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
|
baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
|
||||||
options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
|
options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION);
|
||||||
if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
|
if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) {
|
||||||
baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
|
baton->colourspacePipeline = VIPS_INTERPRETATION_LAST;
|
||||||
}
|
}
|
||||||
baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
|
baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
|
||||||
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
||||||
@@ -1594,18 +1635,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
// Output
|
// Output
|
||||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||||
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
||||||
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
||||||
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
||||||
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile");
|
||||||
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
Napi::Object withExif = options.Get("withExif").As<Napi::Object>();
|
||||||
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
Napi::Array withExifKeys = withExif.GetPropertyNames();
|
||||||
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
for (unsigned int i = 0; i < withExifKeys.Length(); i++) {
|
||||||
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
std::string k = sharp::AttrAsStr(withExifKeys, i);
|
||||||
if (mdStrs.HasOwnProperty(k)) {
|
if (withExif.HasOwnProperty(k)) {
|
||||||
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
||||||
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
||||||
// Format-specific
|
// Format-specific
|
||||||
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
||||||
@@ -1669,6 +1711,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
|
options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
|
||||||
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
||||||
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
||||||
|
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
||||||
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
||||||
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
||||||
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ struct PipelineBaton {
|
|||||||
std::string fileOut;
|
std::string fileOut;
|
||||||
void *bufferOut;
|
void *bufferOut;
|
||||||
size_t bufferOutLength;
|
size_t bufferOutLength;
|
||||||
|
int pageHeightOut;
|
||||||
|
int pagesOut;
|
||||||
std::vector<Composite *> composite;
|
std::vector<Composite *> composite;
|
||||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||||
int topOffsetPre;
|
int topOffsetPre;
|
||||||
@@ -69,14 +71,15 @@ struct PipelineBaton {
|
|||||||
bool premultiplied;
|
bool premultiplied;
|
||||||
bool tileCentre;
|
bool tileCentre;
|
||||||
bool fastShrinkOnLoad;
|
bool fastShrinkOnLoad;
|
||||||
double tintA;
|
std::vector<double> tint;
|
||||||
double tintB;
|
|
||||||
bool flatten;
|
bool flatten;
|
||||||
std::vector<double> flattenBackground;
|
std::vector<double> flattenBackground;
|
||||||
bool unflatten;
|
bool unflatten;
|
||||||
bool negate;
|
bool negate;
|
||||||
bool negateAlpha;
|
bool negateAlpha;
|
||||||
double blurSigma;
|
double blurSigma;
|
||||||
|
VipsPrecision precision;
|
||||||
|
double minAmpl;
|
||||||
double brightness;
|
double brightness;
|
||||||
double saturation;
|
double saturation;
|
||||||
int hue;
|
int hue;
|
||||||
@@ -182,19 +185,21 @@ struct PipelineBaton {
|
|||||||
int heifEffort;
|
int heifEffort;
|
||||||
std::string heifChromaSubsampling;
|
std::string heifChromaSubsampling;
|
||||||
bool heifLossless;
|
bool heifLossless;
|
||||||
|
int heifBitdepth;
|
||||||
double jxlDistance;
|
double jxlDistance;
|
||||||
int jxlDecodingTier;
|
int jxlDecodingTier;
|
||||||
int jxlEffort;
|
int jxlEffort;
|
||||||
bool jxlLossless;
|
bool jxlLossless;
|
||||||
VipsBandFormat rawDepth;
|
VipsBandFormat rawDepth;
|
||||||
std::string err;
|
std::string err;
|
||||||
bool withMetadata;
|
int keepMetadata;
|
||||||
int withMetadataOrientation;
|
int withMetadataOrientation;
|
||||||
double withMetadataDensity;
|
double withMetadataDensity;
|
||||||
std::string withMetadataIcc;
|
std::string withIccProfile;
|
||||||
std::unordered_map<std::string, std::string> withMetadataStrs;
|
std::unordered_map<std::string, std::string> withExif;
|
||||||
|
bool withExifMerge;
|
||||||
int timeoutSeconds;
|
int timeoutSeconds;
|
||||||
std::unique_ptr<double[]> convKernel;
|
std::vector<double> convKernel;
|
||||||
int convKernelWidth;
|
int convKernelWidth;
|
||||||
int convKernelHeight;
|
int convKernelHeight;
|
||||||
double convKernelScale;
|
double convKernelScale;
|
||||||
@@ -205,7 +210,7 @@ struct PipelineBaton {
|
|||||||
int extractChannel;
|
int extractChannel;
|
||||||
bool removeAlpha;
|
bool removeAlpha;
|
||||||
double ensureAlpha;
|
double ensureAlpha;
|
||||||
VipsInterpretation colourspaceInput;
|
VipsInterpretation colourspacePipeline;
|
||||||
VipsInterpretation colourspace;
|
VipsInterpretation colourspace;
|
||||||
std::vector<int> delay;
|
std::vector<int> delay;
|
||||||
int loop;
|
int loop;
|
||||||
@@ -220,11 +225,13 @@ struct PipelineBaton {
|
|||||||
VipsForeignDzDepth tileDepth;
|
VipsForeignDzDepth tileDepth;
|
||||||
std::string tileId;
|
std::string tileId;
|
||||||
std::string tileBasename;
|
std::string tileBasename;
|
||||||
std::unique_ptr<double[]> recombMatrix;
|
std::vector<double> recombMatrix;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
input(nullptr),
|
input(nullptr),
|
||||||
bufferOutLength(0),
|
bufferOutLength(0),
|
||||||
|
pageHeightOut(0),
|
||||||
|
pagesOut(0),
|
||||||
topOffsetPre(-1),
|
topOffsetPre(-1),
|
||||||
topOffsetPost(-1),
|
topOffsetPost(-1),
|
||||||
channels(0),
|
channels(0),
|
||||||
@@ -239,8 +246,7 @@ struct PipelineBaton {
|
|||||||
attentionX(0),
|
attentionX(0),
|
||||||
attentionY(0),
|
attentionY(0),
|
||||||
premultiplied(false),
|
premultiplied(false),
|
||||||
tintA(128.0),
|
tint{ -1.0, 0.0, 0.0, 0.0 },
|
||||||
tintB(128.0),
|
|
||||||
flatten(false),
|
flatten(false),
|
||||||
flattenBackground{ 0.0, 0.0, 0.0 },
|
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||||
unflatten(false),
|
unflatten(false),
|
||||||
@@ -350,14 +356,16 @@ struct PipelineBaton {
|
|||||||
heifEffort(4),
|
heifEffort(4),
|
||||||
heifChromaSubsampling("4:4:4"),
|
heifChromaSubsampling("4:4:4"),
|
||||||
heifLossless(false),
|
heifLossless(false),
|
||||||
|
heifBitdepth(8),
|
||||||
jxlDistance(1.0),
|
jxlDistance(1.0),
|
||||||
jxlDecodingTier(0),
|
jxlDecodingTier(0),
|
||||||
jxlEffort(7),
|
jxlEffort(7),
|
||||||
jxlLossless(false),
|
jxlLossless(false),
|
||||||
rawDepth(VIPS_FORMAT_UCHAR),
|
rawDepth(VIPS_FORMAT_UCHAR),
|
||||||
withMetadata(false),
|
keepMetadata(0),
|
||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
withMetadataDensity(0.0),
|
withMetadataDensity(0.0),
|
||||||
|
withExifMerge(true),
|
||||||
timeoutSeconds(0),
|
timeoutSeconds(0),
|
||||||
convKernelWidth(0),
|
convKernelWidth(0),
|
||||||
convKernelHeight(0),
|
convKernelHeight(0),
|
||||||
@@ -369,7 +377,7 @@ struct PipelineBaton {
|
|||||||
extractChannel(-1),
|
extractChannel(-1),
|
||||||
removeAlpha(false),
|
removeAlpha(false),
|
||||||
ensureAlpha(-1.0),
|
ensureAlpha(-1.0),
|
||||||
colourspaceInput(VIPS_INTERPRETATION_LAST),
|
colourspacePipeline(VIPS_INTERPRETATION_LAST),
|
||||||
colourspace(VIPS_INTERPRETATION_LAST),
|
colourspace(VIPS_INTERPRETATION_LAST),
|
||||||
loop(-1),
|
loop(-1),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
|
|||||||
version.Set("isGlobal", Napi::Boolean::New(env, true));
|
version.Set("isGlobal", Napi::Boolean::New(env, true));
|
||||||
#else
|
#else
|
||||||
version.Set("isGlobal", Napi::Boolean::New(env, false));
|
version.Set("isGlobal", Napi::Boolean::New(env, false));
|
||||||
|
#endif
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
version.Set("isWasm", Napi::Boolean::New(env, true));
|
||||||
|
#else
|
||||||
|
version.Set("isWasm", Napi::Boolean::New(env, false));
|
||||||
#endif
|
#endif
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:23.10
|
||||||
ARG BRANCH=main
|
ARG BRANCH=main
|
||||||
|
|
||||||
# Install basic dependencies
|
# Install basic dependencies
|
||||||
RUN apt-get -y update && apt-get install -y build-essential curl git
|
RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg
|
||||||
|
|
||||||
# Install latest Node.js LTS
|
# Install latest Node.js LTS
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
|
RUN mkdir -p /etc/apt/keyrings
|
||||||
RUN apt-get install -y nodejs
|
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
|
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||||
|
RUN apt-get -y update && apt-get install -y nodejs
|
||||||
|
|
||||||
# Install benchmark dependencies
|
# Install benchmark dependencies
|
||||||
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
|
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@squoosh/cli": "0.7.3",
|
"@squoosh/cli": "0.7.3",
|
||||||
"@squoosh/lib": "0.5.3",
|
"@squoosh/lib": "0.5.3",
|
||||||
"async": "3.2.4",
|
"async": "3.2.5",
|
||||||
"benchmark": "2.1.4",
|
"benchmark": "2.1.4",
|
||||||
"gm": "1.25.0",
|
"gm": "1.25.0",
|
||||||
"imagemagick": "0.1.3",
|
"imagemagick": "0.1.3",
|
||||||
"jimp": "0.22.10"
|
"jimp": "0.22.10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tensorflow/tfjs-node": "4.11.0",
|
"@tensorflow/tfjs-node": "4.13.0",
|
||||||
"mapnik": "4.5.9"
|
"mapnik": "4.5.9"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
|
|||||||
BIN
test/fixtures/XCMYK 2017.icc
vendored
Normal file
BIN
test/fixtures/d.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
test/fixtures/expected/colourspace.cmyk-to-cmyk-negated.tif
vendored
Normal file
BIN
test/fixtures/expected/d-opacity-30.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
test/fixtures/expected/flip-and-flop.jpg
vendored
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
BIN
test/fixtures/expected/negate-trans.png
vendored
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
test/fixtures/expected/tint-alpha.png
vendored
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 135 KiB |
BIN
test/fixtures/expected/tint-blue.jpg
vendored
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-cmyk.jpg
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/tint-green.jpg
vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
BIN
test/fixtures/expected/tint-red.jpg
vendored
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-sepia.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/fogra-0-100-100-0.tif
vendored
Normal file
80
test/fixtures/index.js
vendored
@@ -14,29 +14,26 @@ const getPath = function (filename) {
|
|||||||
|
|
||||||
// Generates a 64-bit-as-binary-string image fingerprint
|
// Generates a 64-bit-as-binary-string image fingerprint
|
||||||
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
|
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
|
||||||
const fingerprint = function (image, callback) {
|
async function fingerprint (image) {
|
||||||
sharp(image)
|
return sharp(image)
|
||||||
.flatten('gray')
|
.flatten('gray')
|
||||||
.greyscale()
|
.greyscale()
|
||||||
.normalise()
|
.normalise()
|
||||||
.resize(9, 8, { fit: sharp.fit.fill })
|
.resize(9, 8, { fit: sharp.fit.fill })
|
||||||
.raw()
|
.raw()
|
||||||
.toBuffer(function (err, data) {
|
.toBuffer()
|
||||||
if (err) {
|
.then(function (data) {
|
||||||
callback(err);
|
let fingerprint = '';
|
||||||
} else {
|
for (let col = 0; col < 8; col++) {
|
||||||
let fingerprint = '';
|
for (let row = 0; row < 8; row++) {
|
||||||
for (let col = 0; col < 8; col++) {
|
const left = data[(row * 8) + col];
|
||||||
for (let row = 0; row < 8; row++) {
|
const right = data[(row * 8) + col + 1];
|
||||||
const left = data[(row * 8) + col];
|
fingerprint = fingerprint + (left < right ? '1' : '0');
|
||||||
const right = data[(row * 8) + col + 1];
|
|
||||||
fingerprint = fingerprint + (left < right ? '1' : '0');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
callback(null, fingerprint);
|
|
||||||
}
|
}
|
||||||
|
return fingerprint;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -102,6 +99,7 @@ module.exports = {
|
|||||||
inputPngTrimSpecificColour16bit: getPath('Flag_of_the_Netherlands-16bit.png'), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png
|
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
|
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/
|
inputPngUint32Limit: getPath('65536-uint32-limit.png'), // https://alexandre.alapetite.fr/doc-alex/large-image/
|
||||||
|
inputPngWithProPhotoProfile: getPath('prophoto.png'),
|
||||||
|
|
||||||
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
@@ -114,6 +112,7 @@ module.exports = {
|
|||||||
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
|
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
|
||||||
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
||||||
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
|
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
|
||||||
|
inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045
|
||||||
|
|
||||||
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
|
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
|
||||||
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||||
@@ -138,6 +137,7 @@ module.exports = {
|
|||||||
|
|
||||||
testPattern: getPath('test-pattern.png'),
|
testPattern: getPath('test-pattern.png'),
|
||||||
|
|
||||||
|
inputPngWithTransparent: getPath('d.png'),
|
||||||
// Path for tests requiring human inspection
|
// Path for tests requiring human inspection
|
||||||
path: getPath,
|
path: getPath,
|
||||||
|
|
||||||
@@ -149,46 +149,44 @@ module.exports = {
|
|||||||
// Verify similarity of expected vs actual images via fingerprint
|
// Verify similarity of expected vs actual images via fingerprint
|
||||||
// Specify distance threshold using `options={threshold: 42}`, default
|
// Specify distance threshold using `options={threshold: 42}`, default
|
||||||
// `threshold` is 5;
|
// `threshold` is 5;
|
||||||
assertSimilar: function (expectedImage, actualImage, options, callback) {
|
assertSimilar: async function (expectedImage, actualImage, options, callback) {
|
||||||
if (typeof options === 'function') {
|
if (typeof options === 'function') {
|
||||||
callback = options;
|
callback = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
if (typeof options === 'undefined' || options === null) {
|
||||||
if (typeof options === 'undefined' && options === null) {
|
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.threshold === null || typeof options.threshold === 'undefined') {
|
if (options.threshold === null || typeof options.threshold === 'undefined') {
|
||||||
options.threshold = 5; // ~7% threshold
|
options.threshold = 5; // ~7% threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options.threshold !== 'number') {
|
if (typeof options.threshold !== 'number') {
|
||||||
throw new TypeError('`options.threshold` must be a number');
|
throw new TypeError('`options.threshold` must be a number');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof callback !== 'function') {
|
try {
|
||||||
throw new TypeError('`callback` must be a function');
|
const [expectedFingerprint, actualFingerprint] = await Promise.all([
|
||||||
|
fingerprint(expectedImage),
|
||||||
|
fingerprint(actualImage)
|
||||||
|
]);
|
||||||
|
let distance = 0;
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
if (expectedFingerprint[i] !== actualFingerprint[i]) {
|
||||||
|
distance++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (distance > options.threshold) {
|
||||||
|
throw new Error(`Expected maximum similarity distance: ${options.threshold}. Actual: ${distance}.`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (callback) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint(expectedImage, function (err, expectedFingerprint) {
|
|
||||||
if (err) return callback(err);
|
|
||||||
fingerprint(actualImage, function (err, actualFingerprint) {
|
|
||||||
if (err) return callback(err);
|
|
||||||
let distance = 0;
|
|
||||||
for (let i = 0; i < 64; i++) {
|
|
||||||
if (expectedFingerprint[i] !== actualFingerprint[i]) {
|
|
||||||
distance++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distance > options.threshold) {
|
|
||||||
return callback(new Error('Expected maximum similarity distance: ' + options.threshold + '. Actual: ' + distance + '.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) {
|
assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) {
|
||||||
|
|||||||
BIN
test/fixtures/invalid-illuminant.icc
vendored
Normal file
BIN
test/fixtures/prophoto.png
vendored
Normal file
|
After Width: | Height: | Size: 556 B |
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
if ! type valgrind >/dev/null; then
|
if ! type valgrind >/dev/null; then
|
||||||
echo "Please install valgrind before running memory leak tests"
|
echo "Please install valgrind before running memory leak tests"
|
||||||
@@ -7,14 +8,15 @@ fi
|
|||||||
|
|
||||||
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
||||||
|
|
||||||
for test in ./test/unit/*.js; do
|
TESTS=$(ls test/unit --ignore=svg.js --ignore=text.js)
|
||||||
|
for test in $TESTS; do
|
||||||
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 VIPS_NOVECTOR=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/libvips.supp \
|
||||||
--suppressions=test/leak/sharp.supp \
|
--suppressions=test/leak/sharp.supp \
|
||||||
--gen-suppressions=yes \
|
--gen-suppressions=yes \
|
||||||
--leak-check=full \
|
--leak-check=full \
|
||||||
--show-leak-kinds=definite,indirect,possible \
|
--show-leak-kinds=definite,indirect \
|
||||||
--num-callers=20 \
|
--num-callers=20 \
|
||||||
--trace-children=yes \
|
--trace-children=yes \
|
||||||
node --expose-gc node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "$test";
|
node --expose-gc --zero-fill-buffers node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "test/unit/$test";
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -173,41 +173,6 @@
|
|||||||
fun:TIFFWriteEncodedTile
|
fun:TIFFWriteEncodedTile
|
||||||
}
|
}
|
||||||
|
|
||||||
# gsf
|
|
||||||
{
|
|
||||||
param_gsf_output_write
|
|
||||||
Memcheck:Param
|
|
||||||
write(buf)
|
|
||||||
fun:write
|
|
||||||
...
|
|
||||||
fun:gsf_output_write
|
|
||||||
}
|
|
||||||
{
|
|
||||||
value_gsf_output_write_crc32_little
|
|
||||||
Memcheck:Value8
|
|
||||||
fun:crc32_little
|
|
||||||
...
|
|
||||||
fun:gsf_output_write
|
|
||||||
}
|
|
||||||
{
|
|
||||||
param_gsf_new_do_write
|
|
||||||
Memcheck:Param
|
|
||||||
write(buf)
|
|
||||||
...
|
|
||||||
fun:new_do_write
|
|
||||||
...
|
|
||||||
fun:gsf_output_close
|
|
||||||
}
|
|
||||||
{
|
|
||||||
param_gsf_output_write
|
|
||||||
Memcheck:Param
|
|
||||||
write(buf)
|
|
||||||
...
|
|
||||||
fun:new_do_write
|
|
||||||
...
|
|
||||||
fun:gsf_output_write
|
|
||||||
}
|
|
||||||
|
|
||||||
# fontconfig
|
# fontconfig
|
||||||
{
|
{
|
||||||
leak_fontconfig_FcConfigSubstituteWithPat
|
leak_fontconfig_FcConfigSubstituteWithPat
|
||||||
@@ -349,11 +314,39 @@
|
|||||||
fun:heif_context_read_from_reader
|
fun:heif_context_read_from_reader
|
||||||
}
|
}
|
||||||
|
|
||||||
# orc
|
# glib
|
||||||
{
|
{
|
||||||
addr_orcexec
|
leak_glib__tls_get_addr
|
||||||
Memcheck:Addr1
|
Memcheck:Leak
|
||||||
obj:*/orcexec.*
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:malloc
|
||||||
|
fun:allocate_dtv_entry
|
||||||
|
fun:allocate_and_init
|
||||||
|
fun:tls_get_addr_tail
|
||||||
|
fun:__tls_get_addr
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value_g_utf8_make_valid_strlen
|
||||||
|
Memcheck:Value8
|
||||||
|
fun:strlen
|
||||||
|
fun:g_utf8_make_valid
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value_g_utf8_make_valid_strncpy
|
||||||
|
Memcheck:Value8
|
||||||
|
fun:strncpy
|
||||||
|
fun:g_strndup
|
||||||
|
...
|
||||||
|
fun:g_utf8_make_valid
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_g_utf8_make_valid_strncpy
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:strncpy
|
||||||
|
fun:g_strndup
|
||||||
|
...
|
||||||
|
fun:g_utf8_make_valid
|
||||||
}
|
}
|
||||||
|
|
||||||
# libvips
|
# libvips
|
||||||
@@ -943,6 +936,79 @@
|
|||||||
...
|
...
|
||||||
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
|
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
cond_node_Builtins_JSEntry
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:Builtins_JSEntry
|
||||||
|
...
|
||||||
|
fun:uv__poll_io_uring
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_node_Builtins_TestEqualStrictHandler
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:Builtins_TestEqualStrictHandler
|
||||||
|
...
|
||||||
|
fun:uv__poll_io_uring
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_node_Builtins_TestGreaterThanHandler
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:Builtins_TestGreaterThanHandler
|
||||||
|
...
|
||||||
|
fun:uv__poll_io_uring
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_node_AfterStat
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:_ZN4node2fs9AfterStatEP7uv_fs_s
|
||||||
|
...
|
||||||
|
fun:uv__poll_io_uring
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_node_AfterMkdirp
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN4node2fs11AfterMkdirpEP7uv_fs_s
|
||||||
|
fun:_ZN4node24MakeLibuvRequestCallbackI7uv_fs_sPFvPS1_EE7WrapperES2_
|
||||||
|
fun:_ZZZN4node2fs11MKDirpAsyncEP9uv_loop_sP7uv_fs_sPKciPFvS4_EENKUlS4_E_clES4_ENUlS4_E_4_FUNES4_
|
||||||
|
fun:uv__poll_io_uring
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ArrayBufferSweeper_Finalize
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal18ArrayBufferSweeper8FinalizeEv
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_AdjustAmountOfExternalAllocatedMemory
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v87Isolate37AdjustAmountOfExternalAllocatedMemoryEl
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_IncrementalMarkingLimitReached
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal4Heap30IncrementalMarkingLimitReachedEv
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ShouldExpandOldGenerationOnSlowAllocation
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal4Heap41ShouldExpandOldGenerationOnSlowAllocationEPNS0_9LocalHeapENS0_16AllocationOriginE
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ArrayBufferSweeper_SweepingJob_SweepListFull
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal18ArrayBufferSweeper11SweepingJob13SweepListFullEPNS0_15ArrayBufferListE
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ArrayBufferSweeper_SweepingJob_SweepYoung
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal18ArrayBufferSweeper11SweepingJob10SweepYoungEv
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_StartIncrementalMarkingIfAllocationLimitIsReachedBackground
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:_ZN2v88internal4Heap59StartIncrementalMarkingIfAllocationLimitIsReachedBackgroundEv
|
||||||
|
}
|
||||||
{
|
{
|
||||||
addr_v8_ZN2v88internal12_GLOBAL__N_119HandleApiCallHelperILb0EEENS0
|
addr_v8_ZN2v88internal12_GLOBAL__N_119HandleApiCallHelperILb0EEENS0
|
||||||
Memcheck:Addr8
|
Memcheck:Addr8
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ sharp('input.png')
|
|||||||
// sharpened, with metadata, 90% quality WebP image data. Phew!
|
// sharpened, with metadata, 90% quality WebP image data. Phew!
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sharp('input.png')
|
||||||
|
.keepMetadata()
|
||||||
|
.toFile('output.png', (err, info) => {
|
||||||
|
// output.png is an image containing input.png along with all metadata(EXIF, ICC, XMP, IPTC) from input.png
|
||||||
|
})
|
||||||
|
|
||||||
sharp('input.jpg')
|
sharp('input.jpg')
|
||||||
.resize(300, 200)
|
.resize(300, 200)
|
||||||
.toFile('output.jpg', (err: Error) => {
|
.toFile('output.jpg', (err: Error) => {
|
||||||
@@ -53,6 +59,12 @@ sharp('input.jpg')
|
|||||||
|
|
||||||
sharp('input.jpg').resize({ width: 300 }).blur(false).blur(true).toFile('output.jpg');
|
sharp('input.jpg').resize({ width: 300 }).blur(false).blur(true).toFile('output.jpg');
|
||||||
|
|
||||||
|
sharp().blur();
|
||||||
|
sharp().blur(1);
|
||||||
|
sharp().blur({ sigma: 1 });
|
||||||
|
sharp().blur({ sigma: 1, precision: 'approximate' });
|
||||||
|
sharp().blur({ sigma: 1, minAmplitude: 0.8 });
|
||||||
|
|
||||||
sharp({
|
sharp({
|
||||||
create: {
|
create: {
|
||||||
width: 300,
|
width: 300,
|
||||||
@@ -289,6 +301,13 @@ sharp('input.gif')
|
|||||||
[0.2392, 0.4696, 0.0912],
|
[0.2392, 0.4696, 0.0912],
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.recomb([
|
||||||
|
[1,0,0,0],
|
||||||
|
[0,1,0,0],
|
||||||
|
[0,0,1,0],
|
||||||
|
[0,0,0,1],
|
||||||
|
])
|
||||||
|
|
||||||
.modulate({ brightness: 2 })
|
.modulate({ brightness: 2 })
|
||||||
.modulate({ hue: 180 })
|
.modulate({ hue: 180 })
|
||||||
.modulate({ lightness: 10 })
|
.modulate({ lightness: 10 })
|
||||||
@@ -404,7 +423,7 @@ sharp({
|
|||||||
|
|
||||||
// Taken from API documentation at
|
// Taken from API documentation at
|
||||||
// https://sharp.pixelplumbing.com/api-operation#clahe
|
// https://sharp.pixelplumbing.com/api-operation#clahe
|
||||||
// introducted
|
// introduced
|
||||||
sharp('input.jpg').clahe({ width: 10, height: 10 }).toFile('output.jpg');
|
sharp('input.jpg').clahe({ width: 10, height: 10 }).toFile('output.jpg');
|
||||||
|
|
||||||
sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile.jpg');
|
sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile.jpg');
|
||||||
@@ -589,7 +608,7 @@ sharp({
|
|||||||
rgba: true,
|
rgba: true,
|
||||||
justify: true,
|
justify: true,
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
wrap: 'charWord',
|
wrap: 'word-char',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.png()
|
.png()
|
||||||
@@ -659,6 +678,39 @@ sharp('input.tiff').webp({ preset: 'drawing' }).toFile('out.webp');
|
|||||||
sharp('input.tiff').webp({ preset: 'text' }).toFile('out.webp');
|
sharp('input.tiff').webp({ preset: 'text' }).toFile('out.webp');
|
||||||
sharp('input.tiff').webp({ preset: 'default' }).toFile('out.webp');
|
sharp('input.tiff').webp({ preset: 'default' }).toFile('out.webp');
|
||||||
|
|
||||||
// Allow a boolean or an object for metadata options.
|
sharp(input)
|
||||||
// https://github.com/lovell/sharp/issues/3822
|
.keepExif()
|
||||||
sharp(input).withMetadata().withMetadata({}).withMetadata(false);
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
k1: 'v1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withExifMerge({
|
||||||
|
IFD1: {
|
||||||
|
k2: 'v2'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.keepIccProfile()
|
||||||
|
.withIccProfile('filename')
|
||||||
|
.withIccProfile('filename', { attach: false });
|
||||||
|
|
||||||
|
// Added missing types for OverlayOptions
|
||||||
|
// https://github.com/lovell/sharp/pull/4048
|
||||||
|
sharp(input).composite([
|
||||||
|
{
|
||||||
|
input: 'image.gif',
|
||||||
|
animated: true,
|
||||||
|
limitInputPixels: 536805378,
|
||||||
|
density: 144,
|
||||||
|
failOn: "warning"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
sharp(input).composite([
|
||||||
|
{
|
||||||
|
input: 'image.png',
|
||||||
|
animated: false,
|
||||||
|
limitInputPixels: 178935126,
|
||||||
|
density: 72,
|
||||||
|
failOn: "truncated"
|
||||||
|
}
|
||||||
|
])
|
||||||
@@ -144,4 +144,10 @@ describe('AVIF', () => {
|
|||||||
/Processed image is too large for the HEIF format/
|
/Processed image is too large for the HEIF format/
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('Invalid bitdepth value throws error', async () => {
|
||||||
|
assert.rejects(
|
||||||
|
() => sharp().avif({ bitdepth: 11 }),
|
||||||
|
/Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ describe('Blur', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('specific options.sigma 10', function (done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur({ sigma: 10 })
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('specific radius 0.3', function (done) {
|
it('specific radius 0.3', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
@@ -91,4 +104,54 @@ describe('Blur', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid precision', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' });
|
||||||
|
}, /Expected one of: integer, float, approximate for precision but received invalid of type string/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid minAmplitude', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 });
|
||||||
|
}, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/);
|
||||||
|
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 1.01 });
|
||||||
|
}, /Expected number between 0.001 and 1 for minAmplitude but received 1.01 of type number/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 10 and precision approximate', async () => {
|
||||||
|
const approximate = await sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur({ sigma: 10, precision: 'approximate' })
|
||||||
|
.toBuffer();
|
||||||
|
const integer = await sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(10)
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.notDeepEqual(approximate, integer);
|
||||||
|
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), approximate);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 10 and minAmplitude 0.01', async () => {
|
||||||
|
const minAmplitudeLow = await sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur({ sigma: 10, minAmplitude: 0.01 })
|
||||||
|
.toBuffer();
|
||||||
|
const minAmplitudeDefault = await sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(10)
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.notDeepEqual(minAmplitudeLow, minAmplitudeDefault);
|
||||||
|
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('options.sigma is required if options object is passed', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp(fixtures.inputJpg).blur({ precision: 'invalid' });
|
||||||
|
}, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,4 +77,26 @@ describe('Clone', function () {
|
|||||||
assert.strictEqual(0, original.listenerCount('finish'));
|
assert.strictEqual(0, original.listenerCount('finish'));
|
||||||
assert.strictEqual(0, clone.listenerCount('finish'));
|
assert.strictEqual(0, clone.listenerCount('finish'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Ensure deep clone of properties, including arrays', async () => {
|
||||||
|
const alpha = await sharp({
|
||||||
|
create: { width: 320, height: 240, channels: 3, background: 'red' }
|
||||||
|
}).toColourspace('b-w').png().toBuffer();
|
||||||
|
|
||||||
|
const original = sharp();
|
||||||
|
const joiner = original.clone().joinChannel(alpha);
|
||||||
|
const negater = original.clone().negate();
|
||||||
|
|
||||||
|
fs.createReadStream(fixtures.inputJpg320x240).pipe(original);
|
||||||
|
const joined = await joiner.png({ effort: 1 }).toBuffer();
|
||||||
|
const negated = await negater.png({ effort: 1 }).toBuffer();
|
||||||
|
|
||||||
|
const joinedMetadata = await sharp(joined).metadata();
|
||||||
|
assert.strictEqual(joinedMetadata.channels, 4);
|
||||||
|
assert.strictEqual(joinedMetadata.hasAlpha, true);
|
||||||
|
|
||||||
|
const negatedMetadata = await sharp(negated).metadata();
|
||||||
|
assert.strictEqual(negatedMetadata.channels, 3);
|
||||||
|
assert.strictEqual(negatedMetadata.hasAlpha, false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -106,6 +106,43 @@ describe('Colour space conversion', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('CMYK profile to CMYK profile conversion using perceptual intent', async () => {
|
||||||
|
const data = await sharp(fixtures.inputTiffFogra)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toColourspace('cmyk')
|
||||||
|
.pipelineColourspace('cmyk')
|
||||||
|
.withIccProfile(fixtures.path('XCMYK 2017.icc'))
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const [c, m, y, k] = data;
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
{ c, m, y, k },
|
||||||
|
{ c: 1, m: 239, y: 227, k: 5 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CMYK profile to CMYK profile with negate', (done) => {
|
||||||
|
sharp(fixtures.inputTiffFogra)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toColourspace('cmyk')
|
||||||
|
.pipelineColourspace('cmyk')
|
||||||
|
.withIccProfile(fixtures.path('XCMYK 2017.icc'))
|
||||||
|
.negate()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fixtures.assertSimilar(
|
||||||
|
fixtures.expected('colourspace.cmyk-to-cmyk-negated.tif'),
|
||||||
|
data,
|
||||||
|
{ threshold: 0 },
|
||||||
|
done
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', function (done) {
|
it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', function (done) {
|
||||||
sharp(fixtures.inputPngGradients)
|
sharp(fixtures.inputPngGradients)
|
||||||
.pipelineColourspace('rgb16')
|
.pipelineColourspace('rgb16')
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ describe('composite', () => {
|
|||||||
assert.strictEqual(info.height, 40);
|
assert.strictEqual(info.height, 40);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Ensure implict unpremultiply after resize but before composite', async () => {
|
it('Ensure implicit unpremultiply after resize but before composite', async () => {
|
||||||
const [r, g, b, a] = await sharp({
|
const [r, g, b, a] = await sharp({
|
||||||
create: {
|
create: {
|
||||||
width: 1, height: 1, channels: 4, background: 'saddlebrown'
|
width: 1, height: 1, channels: 4, background: 'saddlebrown'
|
||||||
|
|||||||
@@ -113,6 +113,39 @@ describe('Extend', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('extend top with mirroring uses ordered read', async () => {
|
||||||
|
const data = await sharp(fixtures.inputJpg)
|
||||||
|
.extend({
|
||||||
|
extendWith: 'mirror',
|
||||||
|
top: 1
|
||||||
|
})
|
||||||
|
.png({ compressionLevel: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const { width, height } = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(2725, width);
|
||||||
|
assert.strictEqual(2226, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('multi-page extend uses ordered read', async () => {
|
||||||
|
const multiPageTiff = await sharp(fixtures.inputGifAnimated, { animated: true })
|
||||||
|
.resize({ width: 8, height: 48 })
|
||||||
|
.tiff()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const data = await sharp(multiPageTiff, { pages: -1 })
|
||||||
|
.extend({
|
||||||
|
background: 'red',
|
||||||
|
top: 1
|
||||||
|
})
|
||||||
|
.png({ compressionLevel: 0 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const { width, height } = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(8, width);
|
||||||
|
assert.strictEqual(1470, height);
|
||||||
|
});
|
||||||
|
|
||||||
it('missing parameter fails', function () {
|
it('missing parameter fails', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().extend();
|
sharp().extend();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe('GIF input', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
it('Animated GIF first page to PNG', () =>
|
it('Animated GIF first page to non-animated GIF', () =>
|
||||||
sharp(fixtures.inputGifAnimated)
|
sharp(fixtures.inputGifAnimated)
|
||||||
.toBuffer({ resolveWithObject: true })
|
.toBuffer({ resolveWithObject: true })
|
||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
@@ -49,10 +49,12 @@ describe('GIF input', () => {
|
|||||||
assert.strictEqual(80, info.width);
|
assert.strictEqual(80, info.width);
|
||||||
assert.strictEqual(80, info.height);
|
assert.strictEqual(80, info.height);
|
||||||
assert.strictEqual(4, info.channels);
|
assert.strictEqual(4, info.channels);
|
||||||
|
assert.strictEqual(undefined, info.pages);
|
||||||
|
assert.strictEqual(undefined, info.pageHeight);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
it('Animated GIF all pages to PNG "toilet roll"', () =>
|
it('Animated GIF round trip', () =>
|
||||||
sharp(fixtures.inputGifAnimated, { pages: -1 })
|
sharp(fixtures.inputGifAnimated, { pages: -1 })
|
||||||
.toBuffer({ resolveWithObject: true })
|
.toBuffer({ resolveWithObject: true })
|
||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
@@ -62,6 +64,8 @@ describe('GIF input', () => {
|
|||||||
assert.strictEqual(80, info.width);
|
assert.strictEqual(80, info.width);
|
||||||
assert.strictEqual(2400, info.height);
|
assert.strictEqual(2400, info.height);
|
||||||
assert.strictEqual(4, info.channels);
|
assert.strictEqual(4, info.channels);
|
||||||
|
assert.strictEqual(30, info.pages);
|
||||||
|
assert.strictEqual(80, info.pageHeight);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -78,4 +78,21 @@ describe('HEIF', () => {
|
|||||||
sharp().heif({ compression: 'av1', chromaSubsampling: '4:4:4' });
|
sharp().heif({ compression: 'av1', chromaSubsampling: '4:4:4' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('valid bitdepth value does not throw an error', () => {
|
||||||
|
const { heif } = sharp.versions;
|
||||||
|
delete sharp.versions.heif;
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
sharp().heif({ compression: 'av1', bitdepth: 12 });
|
||||||
|
});
|
||||||
|
sharp.versions.heif = '1.2.3';
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().heif({ compression: 'av1', bitdepth: 10 });
|
||||||
|
}, /Error: Expected 8 for bitdepth when using prebuilt binaries but received 10 of type number/);
|
||||||
|
sharp.versions.heif = heif;
|
||||||
|
});
|
||||||
|
it('invalid bitdepth value should throw an error', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().heif({ compression: 'av1', bitdepth: 11 });
|
||||||
|
}, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,20 +66,45 @@ describe('libvips binaries', function () {
|
|||||||
|
|
||||||
delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS;
|
delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS;
|
||||||
});
|
});
|
||||||
|
it('useGlobalLibvips can be forced via an env var', function () {
|
||||||
|
process.env.SHARP_FORCE_GLOBAL_LIBVIPS = 1;
|
||||||
|
|
||||||
|
const useGlobalLibvips = libvips.useGlobalLibvips();
|
||||||
|
assert.strictEqual(true, useGlobalLibvips);
|
||||||
|
|
||||||
|
let logged = false;
|
||||||
|
const logger = function (message) {
|
||||||
|
assert.strictEqual(message, 'Detected SHARP_FORCE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
|
||||||
|
logged = true;
|
||||||
|
};
|
||||||
|
const useGlobalLibvipsWithLogger = libvips.useGlobalLibvips(logger);
|
||||||
|
assert.strictEqual(true, useGlobalLibvipsWithLogger);
|
||||||
|
assert.strictEqual(true, logged);
|
||||||
|
|
||||||
|
delete process.env.SHARP_FORCE_GLOBAL_LIBVIPS;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Build time platform detection', () => {
|
describe('Build time platform detection', () => {
|
||||||
it('Can override platform with npm_config_platform and npm_config_libc', () => {
|
it('Can override platform with npm_config_platform and npm_config_libc', function () {
|
||||||
process.env.npm_config_platform = 'testplatform';
|
process.env.npm_config_platform = 'testplatform';
|
||||||
process.env.npm_config_libc = 'testlibc';
|
process.env.npm_config_libc = 'testlibc';
|
||||||
const [platform] = libvips.buildPlatformArch().split('-');
|
const platformArch = libvips.buildPlatformArch();
|
||||||
|
if (platformArch === 'wasm32') {
|
||||||
|
return this.skip();
|
||||||
|
}
|
||||||
|
const [platform] = platformArch.split('-');
|
||||||
assert.strictEqual(platform, 'testplatformtestlibc');
|
assert.strictEqual(platform, 'testplatformtestlibc');
|
||||||
delete process.env.npm_config_platform;
|
delete process.env.npm_config_platform;
|
||||||
delete process.env.npm_config_libc;
|
delete process.env.npm_config_libc;
|
||||||
});
|
});
|
||||||
it('Can override arch with npm_config_arch', () => {
|
it('Can override arch with npm_config_arch', function () {
|
||||||
process.env.npm_config_arch = 'test';
|
process.env.npm_config_arch = 'test';
|
||||||
const [, arch] = libvips.buildPlatformArch().split('-');
|
const platformArch = libvips.buildPlatformArch();
|
||||||
|
if (platformArch === 'wasm32') {
|
||||||
|
return this.skip();
|
||||||
|
}
|
||||||
|
const [, arch] = platformArch.split('-');
|
||||||
assert.strictEqual(arch, 'test');
|
assert.strictEqual(arch, 'test');
|
||||||
delete process.env.npm_config_arch;
|
delete process.env.npm_config_arch;
|
||||||
});
|
});
|
||||||
@@ -88,27 +113,36 @@ describe('libvips binaries', function () {
|
|||||||
describe('Build time directories', () => {
|
describe('Build time directories', () => {
|
||||||
it('sharp-libvips include', () => {
|
it('sharp-libvips include', () => {
|
||||||
const dir = libvips.buildSharpLibvipsIncludeDir();
|
const dir = libvips.buildSharpLibvipsIncludeDir();
|
||||||
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
if (dir) {
|
||||||
|
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('sharp-libvips cplusplus', () => {
|
it('sharp-libvips cplusplus', () => {
|
||||||
const dir = libvips.buildSharpLibvipsCPlusPlusDir();
|
const dir = libvips.buildSharpLibvipsCPlusPlusDir();
|
||||||
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
if (dir) {
|
||||||
|
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('sharp-libvips lib', () => {
|
it('sharp-libvips lib', () => {
|
||||||
const dir = libvips.buildSharpLibvipsLibDir();
|
const dir = libvips.buildSharpLibvipsLibDir();
|
||||||
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
if (dir) {
|
||||||
|
assert.strictEqual(fs.statSync(dir).isDirectory(), true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Runtime detection', () => {
|
describe('Runtime detection', () => {
|
||||||
it('platform', () => {
|
it('platform', () => {
|
||||||
const [platform] = libvips.runtimePlatformArch().split('-');
|
const [platform] = libvips.runtimePlatformArch().split('-');
|
||||||
assert.strict(['darwin', 'linux', 'linuxmusl', 'win32'].includes(platform));
|
assert.strict(['darwin', 'freebsd', 'linux', 'linuxmusl', 'win32'].includes(platform));
|
||||||
});
|
});
|
||||||
it('arch', () => {
|
it('arch', () => {
|
||||||
const [, arch] = libvips.runtimePlatformArch().split('-');
|
const [, arch] = libvips.runtimePlatformArch().split('-');
|
||||||
assert.strict(['arm', 'arm64', 'ia32', 'x64'].includes(arch));
|
assert.strict(['arm', 'arm64', 'ia32', 'x64'].includes(arch));
|
||||||
});
|
});
|
||||||
|
it('isUnsupportedNodeRuntime', () => {
|
||||||
|
assert.strictEqual(libvips.isUnsupportedNodeRuntime(), undefined);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logger', function () {
|
describe('logger', function () {
|
||||||
@@ -136,4 +170,26 @@ describe('libvips binaries', function () {
|
|||||||
libvips.log(new Error('problem'));
|
libvips.log(new Error('problem'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('yarn locator hash', () => {
|
||||||
|
it('known platform', () => {
|
||||||
|
const cc = process.env.CC;
|
||||||
|
delete process.env.CC;
|
||||||
|
process.env.npm_config_platform = 'linux';
|
||||||
|
process.env.npm_config_arch = 's390x';
|
||||||
|
process.env.npm_config_libc = '';
|
||||||
|
const locatorHash = libvips.yarnLocator();
|
||||||
|
assert.strictEqual(locatorHash, 'c4ea54fdc1');
|
||||||
|
delete process.env.npm_config_platform;
|
||||||
|
delete process.env.npm_config_arch;
|
||||||
|
delete process.env.npm_config_libc;
|
||||||
|
process.env.CC = cc;
|
||||||
|
});
|
||||||
|
it('unknown platform', () => {
|
||||||
|
process.env.npm_config_platform = 'unknown-platform';
|
||||||
|
const locatorHash = libvips.yarnLocator();
|
||||||
|
assert.strictEqual(locatorHash, '');
|
||||||
|
delete process.env.npm_config_platform;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const icc = require('icc');
|
|||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
const fixtures = require('../fixtures');
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
const create = { width: 1, height: 1, channels: 3, background: 'red' };
|
||||||
|
|
||||||
describe('Image metadata', function () {
|
describe('Image metadata', function () {
|
||||||
it('JPEG', function (done) {
|
it('JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
|
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
|
||||||
@@ -152,6 +154,31 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PNG with comment', function (done) {
|
||||||
|
sharp(fixtures.inputPngTestJoinChannel).metadata(function (err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', metadata.format);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.size);
|
||||||
|
assert.strictEqual(320, metadata.width);
|
||||||
|
assert.strictEqual(240, metadata.height);
|
||||||
|
assert.strictEqual('b-w', metadata.space);
|
||||||
|
assert.strictEqual(1, metadata.channels);
|
||||||
|
assert.strictEqual('uchar', metadata.depth);
|
||||||
|
assert.strictEqual(72, metadata.density);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.chromaSubsampling);
|
||||||
|
assert.strictEqual(false, metadata.isProgressive);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.orientation);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.exif);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.icc);
|
||||||
|
assert.strictEqual(1, metadata.comments.length);
|
||||||
|
assert.strictEqual('Comment', metadata.comments[0].keyword);
|
||||||
|
assert.strictEqual('Created with GIMP', metadata.comments[0].text);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Transparent PNG', function (done) {
|
it('Transparent PNG', function (done) {
|
||||||
sharp(fixtures.inputPngWithTransparency).metadata(function (err, metadata) {
|
sharp(fixtures.inputPngWithTransparency).metadata(function (err, metadata) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
@@ -552,11 +579,97 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keep existing ICC profile', async () => {
|
||||||
|
const data = await sharp(fixtures.inputJpgWithExif)
|
||||||
|
.keepIccProfile()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
const { description } = icc.parse(metadata.icc);
|
||||||
|
assert.strictEqual(description, 'Generic RGB Profile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keep existing ICC profile, ignore colourspace conversion', async () => {
|
||||||
|
const data = await sharp(fixtures.inputJpgWithExif)
|
||||||
|
.keepIccProfile()
|
||||||
|
.toColourspace('cmyk')
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(metadata.channels, 3);
|
||||||
|
const { description } = icc.parse(metadata.icc);
|
||||||
|
assert.strictEqual(description, 'Generic RGB Profile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keep existing ICC profile, avoid colour transform', async () => {
|
||||||
|
const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile)
|
||||||
|
.keepIccProfile()
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.strictEqual(r, 131);
|
||||||
|
assert.strictEqual(g, 141);
|
||||||
|
assert.strictEqual(b, 192);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keep existing CMYK ICC profile', async () => {
|
||||||
|
const data = await sharp(fixtures.inputJpgWithCmykProfile)
|
||||||
|
.pipelineColourspace('cmyk')
|
||||||
|
.toColourspace('cmyk')
|
||||||
|
.keepIccProfile()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(metadata.channels, 4);
|
||||||
|
const { description } = icc.parse(metadata.icc);
|
||||||
|
assert.strictEqual(description, 'U.S. Web Coated (SWOP) v2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transform to ICC profile and attach', async () => {
|
||||||
|
const data = await sharp({ create })
|
||||||
|
.png()
|
||||||
|
.withIccProfile('p3', { attach: true })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
const { description } = icc.parse(metadata.icc);
|
||||||
|
assert.strictEqual(description, 'sP3C');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transform to ICC profile but do not attach', async () => {
|
||||||
|
const data = await sharp({ create })
|
||||||
|
.png()
|
||||||
|
.withIccProfile('p3', { attach: false })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(undefined, metadata.icc);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transform to invalid ICC profile emits warning', async () => {
|
||||||
|
const img = sharp({ create })
|
||||||
|
.png()
|
||||||
|
.withIccProfile(fixtures.path('invalid-illuminant.icc'));
|
||||||
|
|
||||||
|
const warningsEmitted = [];
|
||||||
|
img.on('warning', (warning) => {
|
||||||
|
warningsEmitted.push(warning);
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await img.toBuffer();
|
||||||
|
assert.strict(warningsEmitted.includes('Invalid profile'));
|
||||||
|
|
||||||
|
const metadata = await sharp(data).metadata();
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(undefined, metadata.icc);
|
||||||
|
});
|
||||||
|
|
||||||
it('Apply CMYK output ICC profile', function (done) {
|
it('Apply CMYK output ICC profile', function (done) {
|
||||||
const output = fixtures.path('output.icc-cmyk.jpg');
|
const output = fixtures.path('output.icc-cmyk.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(64)
|
.resize(64)
|
||||||
.withMetadata({ icc: 'cmyk' })
|
.withIccProfile('cmyk')
|
||||||
.toFile(output, function (err) {
|
.toFile(output, function (err) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
sharp(output).metadata(function (err, metadata) {
|
sharp(output).metadata(function (err, metadata) {
|
||||||
@@ -581,7 +694,7 @@ describe('Image metadata', function () {
|
|||||||
const output = fixtures.path('output.hilutite.jpg');
|
const output = fixtures.path('output.hilutite.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(64)
|
.resize(64)
|
||||||
.withMetadata({ icc: fixtures.path('hilutite.icm') })
|
.withIccProfile(fixtures.path('hilutite.icm'))
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('hilutite.jpg'), 9);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('hilutite.jpg'), 9);
|
||||||
@@ -620,7 +733,6 @@ describe('Image metadata', function () {
|
|||||||
it('Remove EXIF metadata after a resize', function (done) {
|
it('Remove EXIF metadata after a resize', function (done) {
|
||||||
sharp(fixtures.inputJpgWithExif)
|
sharp(fixtures.inputJpgWithExif)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.withMetadata(false)
|
|
||||||
.toBuffer(function (err, buffer) {
|
.toBuffer(function (err, buffer) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
sharp(buffer).metadata(function (err, metadata) {
|
sharp(buffer).metadata(function (err, metadata) {
|
||||||
@@ -651,14 +763,7 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Add EXIF metadata to JPEG', async () => {
|
it('Add EXIF metadata to JPEG', async () => {
|
||||||
const data = await sharp({
|
const data = await sharp({ create })
|
||||||
create: {
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
channels: 3,
|
|
||||||
background: 'red'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.jpeg()
|
.jpeg()
|
||||||
.withMetadata({
|
.withMetadata({
|
||||||
exif: {
|
exif: {
|
||||||
@@ -675,14 +780,7 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Set density of JPEG', async () => {
|
it('Set density of JPEG', async () => {
|
||||||
const data = await sharp({
|
const data = await sharp({ create })
|
||||||
create: {
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
channels: 3,
|
|
||||||
background: 'red'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.withMetadata({
|
.withMetadata({
|
||||||
density: 300
|
density: 300
|
||||||
})
|
})
|
||||||
@@ -694,14 +792,7 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Set density of PNG', async () => {
|
it('Set density of PNG', async () => {
|
||||||
const data = await sharp({
|
const data = await sharp({ create })
|
||||||
create: {
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
channels: 3,
|
|
||||||
background: 'red'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.withMetadata({
|
.withMetadata({
|
||||||
density: 96
|
density: 96
|
||||||
})
|
})
|
||||||
@@ -809,11 +900,7 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('withMetadata adds default sRGB profile to RGB16', async () => {
|
it('withMetadata adds default sRGB profile to RGB16', async () => {
|
||||||
const data = await sharp({
|
const data = await sharp({ create })
|
||||||
create: {
|
|
||||||
width: 8, height: 8, channels: 4, background: 'orange'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.toColorspace('rgb16')
|
.toColorspace('rgb16')
|
||||||
.png()
|
.png()
|
||||||
.withMetadata()
|
.withMetadata()
|
||||||
@@ -827,11 +914,7 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('withMetadata adds P3 profile to 16-bit PNG', async () => {
|
it('withMetadata adds P3 profile to 16-bit PNG', async () => {
|
||||||
const data = await sharp({
|
const data = await sharp({ create })
|
||||||
create: {
|
|
||||||
width: 8, height: 8, channels: 4, background: 'orange'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.toColorspace('rgb16')
|
.toColorspace('rgb16')
|
||||||
.png()
|
.png()
|
||||||
.withMetadata({ icc: 'p3' })
|
.withMetadata({ icc: 'p3' })
|
||||||
@@ -871,7 +954,89 @@ describe('Image metadata', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Invalid withMetadata parameters', function () {
|
it('keepExif maintains all EXIF metadata', async () => {
|
||||||
|
const data1 = await sharp({ create })
|
||||||
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'Test 1',
|
||||||
|
Software: 'sharp'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const data2 = await sharp(data1)
|
||||||
|
.keepExif()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const md2 = await sharp(data2).metadata();
|
||||||
|
const exif2 = exifReader(md2.exif);
|
||||||
|
assert.strictEqual(exif2.Image.Copyright, 'Test 1');
|
||||||
|
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withExif replaces all EXIF metadata', async () => {
|
||||||
|
const data1 = await sharp({ create })
|
||||||
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'Test 1',
|
||||||
|
Software: 'sharp'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const md1 = await sharp(data1).metadata();
|
||||||
|
const exif1 = exifReader(md1.exif);
|
||||||
|
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||||
|
assert.strictEqual(exif1.Image.Software, 'sharp');
|
||||||
|
|
||||||
|
const data2 = await sharp(data1)
|
||||||
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'Test 2'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const md2 = await sharp(data2).metadata();
|
||||||
|
const exif2 = exifReader(md2.exif);
|
||||||
|
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||||
|
assert.strictEqual(exif2.Image.Software, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withExifMerge merges all EXIF metadata', async () => {
|
||||||
|
const data1 = await sharp({ create })
|
||||||
|
.withExif({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'Test 1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const md1 = await sharp(data1).metadata();
|
||||||
|
const exif1 = exifReader(md1.exif);
|
||||||
|
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||||
|
assert.strictEqual(exif1.Image.Software, undefined);
|
||||||
|
|
||||||
|
const data2 = await sharp(data1)
|
||||||
|
.withExifMerge({
|
||||||
|
IFD0: {
|
||||||
|
Copyright: 'Test 2',
|
||||||
|
Software: 'sharp'
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const md2 = await sharp(data2).metadata();
|
||||||
|
const exif2 = exifReader(md2.exif);
|
||||||
|
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||||
|
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid parameters', function () {
|
||||||
it('String orientation', function () {
|
it('String orientation', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().withMetadata({ orientation: 'zoinks' });
|
sharp().withMetadata({ orientation: 'zoinks' });
|
||||||
@@ -922,5 +1087,22 @@ describe('Image metadata', function () {
|
|||||||
sharp().withMetadata({ exif: { ifd0: { fail: false } } });
|
sharp().withMetadata({ exif: { ifd0: { fail: false } } });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('withIccProfile invalid profile', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().withIccProfile(false),
|
||||||
|
/Expected string for icc but received false of type boolean/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('withIccProfile missing attach', () => {
|
||||||
|
assert.doesNotThrow(
|
||||||
|
() => sharp().withIccProfile('test', {})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('withIccProfile invalid attach', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => sharp().withIccProfile('test', { attach: 1 }),
|
||||||
|
/Expected boolean for attach but received 1 of type number/
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,6 +121,29 @@ describe('Recomb', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies opacity 30% to the image', function (done) {
|
||||||
|
const output = fixtures.path('output.recomb-opacity.png');
|
||||||
|
sharp(fixtures.inputPngWithTransparent)
|
||||||
|
.recomb([
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 0.3]
|
||||||
|
])
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(48, info.width);
|
||||||
|
assert.strictEqual(48, info.height);
|
||||||
|
fixtures.assertMaxColourDistance(
|
||||||
|
output,
|
||||||
|
fixtures.expected('d-opacity-30.png'),
|
||||||
|
17
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('invalid matrix specification', function () {
|
describe('invalid matrix specification', function () {
|
||||||
it('missing', function () {
|
it('missing', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
|
|||||||
@@ -353,6 +353,21 @@ describe('Rotation', function () {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('Animated image rotate 180', () =>
|
||||||
|
assert.doesNotReject(() => sharp(fixtures.inputGifAnimated, { animated: true })
|
||||||
|
.rotate(180)
|
||||||
|
.toBuffer()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
it('Animated image rotate non-180 rejects', () =>
|
||||||
|
assert.rejects(() => sharp(fixtures.inputGifAnimated, { animated: true })
|
||||||
|
.rotate(90)
|
||||||
|
.toBuffer(),
|
||||||
|
/Rotate is not supported for multi-page images/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
it('Multiple rotate emits warning', () => {
|
it('Multiple rotate emits warning', () => {
|
||||||
let warningMessage = '';
|
let warningMessage = '';
|
||||||
const s = sharp();
|
const s = sharp();
|
||||||
@@ -402,6 +417,7 @@ describe('Rotation', function () {
|
|||||||
it('Flip and flop', function (done) {
|
it('Flip and flop', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320)
|
.resize(320)
|
||||||
|
.flip()
|
||||||
.flop()
|
.flop()
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ describe('Text to image', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('png', info.format);
|
assert.strictEqual('png', info.format);
|
||||||
assert.strictEqual(3, info.channels);
|
assert.strictEqual(3, info.channels);
|
||||||
assert.ok(inRange(info.width, 450, 550), `Actual width ${info.width}`);
|
assert.ok(inRange(info.width, 400, 600), `Actual width ${info.width}`);
|
||||||
assert.ok(inRange(info.height, 300, 450), `Actual height ${info.height}`);
|
assert.ok(inRange(info.height, 300, 500), `Actual height ${info.height}`);
|
||||||
assert.ok(inRange(info.textAutofitDpi, 900, 1200), `Actual textAutofitDpi ${info.textAutofitDpi}`);
|
assert.ok(inRange(info.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -228,26 +228,34 @@ describe('Text to image', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad width input', function () {
|
it('invalid width', () => {
|
||||||
assert.throws(function () {
|
assert.throws(
|
||||||
sharp({
|
() => sharp({ text: { text: 'text', width: 'bad' } }),
|
||||||
text: {
|
/Expected positive integer for text\.width but received bad of type string/
|
||||||
text: 'text',
|
);
|
||||||
width: 'bad'
|
assert.throws(
|
||||||
}
|
() => sharp({ text: { text: 'text', width: 0.1 } }),
|
||||||
});
|
/Expected positive integer for text\.width but received 0.1 of type number/
|
||||||
});
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp({ text: { text: 'text', width: -1 } }),
|
||||||
|
/Expected positive integer for text\.width but received -1 of type number/
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad height input', function () {
|
it('invalid height', () => {
|
||||||
assert.throws(function () {
|
assert.throws(
|
||||||
sharp({
|
() => sharp({ text: { text: 'text', height: 'bad' } }),
|
||||||
text: {
|
/Expected positive integer for text\.height but received bad of type string/
|
||||||
text: 'text',
|
);
|
||||||
height: 'bad'
|
assert.throws(
|
||||||
}
|
() => sharp({ text: { text: 'text', height: 0.1 } }),
|
||||||
});
|
/Expected positive integer for text\.height but received 0.1 of type number/
|
||||||
});
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp({ text: { text: 'text', height: -1 } }),
|
||||||
|
/Expected positive integer for text\.height but received -1 of type number/
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad align input', function () {
|
it('bad align input', function () {
|
||||||
@@ -272,15 +280,19 @@ describe('Text to image', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad dpi input', function () {
|
it('invalid dpi', () => {
|
||||||
assert.throws(function () {
|
assert.throws(
|
||||||
sharp({
|
() => sharp({ text: { text: 'text', dpi: 'bad' } }),
|
||||||
text: {
|
/Expected integer between 1 and 1000000 for text\.dpi but received bad of type string/
|
||||||
text: 'text',
|
);
|
||||||
dpi: -10
|
assert.throws(
|
||||||
}
|
() => sharp({ text: { text: 'text', dpi: 0.1 } }),
|
||||||
});
|
/Expected integer between 1 and 1000000 for text\.dpi but received 0.1 of type number/
|
||||||
});
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp({ text: { text: 'text', dpi: -1 } }),
|
||||||
|
/Expected integer between 1 and 1000000 for text\.dpi but received -1 of type number/
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad rgba input', function () {
|
it('bad rgba input', function () {
|
||||||
@@ -294,15 +306,19 @@ describe('Text to image', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bad spacing input', function () {
|
it('invalid spacing', () => {
|
||||||
assert.throws(function () {
|
assert.throws(
|
||||||
sharp({
|
() => sharp({ text: { text: 'text', spacing: 'bad' } }),
|
||||||
text: {
|
/Expected integer between -1000000 and 1000000 for text\.spacing but received bad of type string/
|
||||||
text: 'text',
|
);
|
||||||
spacing: 'number expected'
|
assert.throws(
|
||||||
}
|
() => sharp({ text: { text: 'text', spacing: 0.1 } }),
|
||||||
});
|
/Expected integer between -1000000 and 1000000 for text\.spacing but received 0.1 of type number/
|
||||||
});
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => sharp({ text: { text: 'text', spacing: -1000001 } }),
|
||||||
|
/Expected integer between -1000000 and 1000000 for text\.spacing but received -1000001 of type number/
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only height or dpi not both', function () {
|
it('only height or dpi not both', function () {
|
||||||
@@ -319,21 +335,21 @@ describe('Text to image', function () {
|
|||||||
|
|
||||||
it('valid wrap throws', () => {
|
it('valid wrap throws', () => {
|
||||||
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'none' } }));
|
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'none' } }));
|
||||||
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'wordChar' } }));
|
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'word-char' } }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invalid wrap throws', () => {
|
it('invalid wrap throws', () => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => sharp({ text: { text: 'text', wrap: 1 } }),
|
() => sharp({ text: { text: 'text', wrap: 1 } }),
|
||||||
/Expected one of: word, char, wordChar, none for text\.wrap but received 1 of type number/
|
/Expected one of: word, char, word-char, none for text\.wrap but received 1 of type number/
|
||||||
);
|
);
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => sharp({ text: { text: 'text', wrap: false } }),
|
() => sharp({ text: { text: 'text', wrap: false } }),
|
||||||
/Expected one of: word, char, wordChar, none for text\.wrap but received false of type boolean/
|
/Expected one of: word, char, word-char, none for text\.wrap but received false of type boolean/
|
||||||
);
|
);
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => sharp({ text: { text: 'text', wrap: 'invalid' } }),
|
() => sharp({ text: { text: 'text', wrap: 'invalid' } }),
|
||||||
/Expected one of: word, char, wordChar, none for text\.wrap but received invalid of type string/
|
/Expected one of: word, char, word-char, none for text\.wrap but received invalid of type string/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,16 +8,19 @@ const assert = require('assert');
|
|||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
const fixtures = require('../fixtures');
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
// Allow for small rounding differences between platforms
|
||||||
|
const maxDistance = 6;
|
||||||
|
|
||||||
describe('Tint', function () {
|
describe('Tint', function () {
|
||||||
it('tints rgb image red', function (done) {
|
it('tints rgb image red', function (done) {
|
||||||
const output = fixtures.path('output.tint-red.jpg');
|
const output = fixtures.path('output.tint-red.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#FF0000')
|
.tint('#FF0000')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), 18);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -25,12 +28,12 @@ describe('Tint', function () {
|
|||||||
it('tints rgb image green', function (done) {
|
it('tints rgb image green', function (done) {
|
||||||
const output = fixtures.path('output.tint-green.jpg');
|
const output = fixtures.path('output.tint-green.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#00FF00')
|
.tint('#00FF00')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), 27);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -38,12 +41,12 @@ describe('Tint', function () {
|
|||||||
it('tints rgb image blue', function (done) {
|
it('tints rgb image blue', function (done) {
|
||||||
const output = fixtures.path('output.tint-blue.jpg');
|
const output = fixtures.path('output.tint-blue.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#0000FF')
|
.tint('#0000FF')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), 14);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -51,13 +54,13 @@ describe('Tint', function () {
|
|||||||
it('tints rgb image with sepia tone', function (done) {
|
it('tints rgb image with sepia tone', function (done) {
|
||||||
const output = fixtures.path('output.tint-sepia-hex.jpg');
|
const output = fixtures.path('output.tint-sepia-hex.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#704214')
|
.tint('#704214')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(320, info.width);
|
assert.strictEqual(320, info.width);
|
||||||
assert.strictEqual(240, info.height);
|
assert.strictEqual(240, info.height);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -65,13 +68,13 @@ describe('Tint', function () {
|
|||||||
it('tints rgb image with sepia tone with rgb colour', function (done) {
|
it('tints rgb image with sepia tone with rgb colour', function (done) {
|
||||||
const output = fixtures.path('output.tint-sepia-rgb.jpg');
|
const output = fixtures.path('output.tint-sepia-rgb.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint([112, 66, 20])
|
.tint([112, 66, 20])
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(320, info.width);
|
assert.strictEqual(320, info.width);
|
||||||
assert.strictEqual(240, info.height);
|
assert.strictEqual(240, info.height);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -79,13 +82,13 @@ describe('Tint', function () {
|
|||||||
it('tints rgb image with alpha channel', function (done) {
|
it('tints rgb image with alpha channel', function (done) {
|
||||||
const output = fixtures.path('output.tint-alpha.png');
|
const output = fixtures.path('output.tint-alpha.png');
|
||||||
sharp(fixtures.inputPngRGBWithAlpha)
|
sharp(fixtures.inputPngRGBWithAlpha)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#704214')
|
.tint('#704214')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(320, info.width);
|
assert.strictEqual(320, info.width);
|
||||||
assert.strictEqual(240, info.height);
|
assert.strictEqual(240, info.height);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), 10);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -93,12 +96,12 @@ describe('Tint', function () {
|
|||||||
it('tints cmyk image red', function (done) {
|
it('tints cmyk image red', function (done) {
|
||||||
const output = fixtures.path('output.tint-cmyk.jpg');
|
const output = fixtures.path('output.tint-cmyk.jpg');
|
||||||
sharp(fixtures.inputJpgWithCmykProfile)
|
sharp(fixtures.inputJpgWithCmykProfile)
|
||||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
.resize(320, 240)
|
||||||
.tint('#FF0000')
|
.tint('#FF0000')
|
||||||
.toFile(output, function (err, info) {
|
.toFile(output, function (err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), 15);
|
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), maxDistance);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,16 +9,19 @@ const sharp = require('../../');
|
|||||||
|
|
||||||
describe('Utilities', function () {
|
describe('Utilities', function () {
|
||||||
describe('Cache', function () {
|
describe('Cache', function () {
|
||||||
it('Can be disabled', function () {
|
it('Can be disabled', function (done) {
|
||||||
sharp.cache(false);
|
queueMicrotask(() => {
|
||||||
const cache = sharp.cache(false);
|
sharp.cache(false);
|
||||||
assert.strictEqual(cache.memory.current, 0);
|
const cache = sharp.cache(false);
|
||||||
assert.strictEqual(cache.memory.max, 0);
|
assert.strictEqual(cache.memory.current, 0);
|
||||||
assert.strictEqual(typeof cache.memory.high, 'number');
|
assert.strictEqual(cache.memory.max, 0);
|
||||||
assert.strictEqual(cache.files.current, 0);
|
assert.strictEqual(typeof cache.memory.high, 'number');
|
||||||
assert.strictEqual(cache.files.max, 0);
|
assert.strictEqual(cache.files.current, 0);
|
||||||
assert.strictEqual(cache.items.current, 0);
|
assert.strictEqual(cache.files.max, 0);
|
||||||
assert.strictEqual(cache.items.max, 0);
|
assert.strictEqual(cache.items.current, 0);
|
||||||
|
assert.strictEqual(cache.items.max, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('Can be enabled with defaults', function () {
|
it('Can be enabled with defaults', function () {
|
||||||
const cache = sharp.cache(true);
|
const cache = sharp.cache(true);
|
||||||
@@ -131,7 +134,7 @@ describe('Utilities', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('input fileSuffix', function () {
|
it('input fileSuffix', function () {
|
||||||
assert.deepStrictEqual(['.jpg', '.jpeg', '.jpe'], sharp.format.jpeg.input.fileSuffix);
|
assert.deepStrictEqual(['.jpg', '.jpeg', '.jpe', '.jfif'], sharp.format.jpeg.input.fileSuffix);
|
||||||
});
|
});
|
||||||
it('output alias', function () {
|
it('output alias', function () {
|
||||||
assert.deepStrictEqual(['jpe', 'jpg'], sharp.format.jpeg.output.alias);
|
assert.deepStrictEqual(['jpe', 'jpg'], sharp.format.jpeg.output.alias);
|
||||||
|
|||||||