Compare commits

...

50 Commits

Author SHA1 Message Date
Lovell Fuller
4b1680c312 Prerelease v0.35.0-rc.0 2026-01-02 11:12:47 +00:00
Lovell Fuller
2346722c0d Upgrade sharp-libvips to v1.3.0-rc.2 2026-01-02 10:58:18 +00:00
Dmytro Tiapukhin
a5e726002c Add margin option to trim operation #4480 2026-01-02 09:33:40 +00:00
Lovell Fuller
d161e45e06 TypeScript: Ensure 'FormatEnum' keys match reality #4475
Renames format.jp2k as format.jp2 for consistency
2026-01-02 08:04:46 +00:00
Lovell Fuller
006d37b2d0 Add AVIF/HEIF 'tune' option to control quality metrics #4227 2026-01-01 22:41:42 +00:00
Lovell Fuller
0d872bd13a Add WebP 'exact' option for control over transparent pixels 2026-01-01 19:19:20 +00:00
Lovell Fuller
1cf4b7f04d Deprecate win32-ia32 prebuilt binaries 2025-12-31 10:16:09 +00:00
Lovell Fuller
e50c0c2e04 CI: Migrate FreeBSD from Cirrus to GitHub Actions 2025-12-31 09:48:59 +00:00
Lovell Fuller
3278a9a913 CI: Pin win32-ia32 Node.js 22 version
There seems to be an Error message string corruption problem in
the latest 22.21.1.
2025-12-30 19:35:51 +00:00
Lovell Fuller
1b2f79335d Remove previously-deprecated properties from API 2025-12-29 13:04:27 +00:00
Lovell Fuller
937167933b Docs: Add list of well-maintained Lambda Layers 2025-12-27 09:03:44 +00:00
Lovell Fuller
dbcb7e60bd Add toUint8Array for output backed by transferable ArrayBuffer #4355 2025-12-23 18:19:01 +00:00
Lovell Fuller
e1bad5470e Remove install script, building from source is now opt-in
The vast majority of people don't need this. Package managers
are (correctly) starting to block install scripts by default,
so this change should reduce install-time warnings.
2025-12-21 12:36:40 +00:00
Lovell Fuller
1a2c1c8833 Add version to shared library filename to help avoid collision 2025-12-21 12:00:30 +00:00
Lovell Fuller
aaeded2b67 Add withGainMap to process HDR JPEGs with embedded gain map #4314 2025-12-19 15:41:09 +00:00
Lovell Fuller
f6cdd36559 Bump devDeps 2025-12-18 22:47:14 +00:00
Lovell Fuller
283c7d3f0c Drop Node.js 18, now requires Node.js >= 20.9.0 2025-12-17 15:08:34 +00:00
Lovell Fuller
34c39fa194 Upgrade to libvips v8.18.0-rc.2 2025-12-17 13:26:51 +00:00
Lovell Fuller
7b4c476243 CI: Update to latest FreeBSD 16 2025-12-16 19:59:31 +00:00
Lovell Fuller
084a30f8bf Docs: clarify metadata 'format' property #4483 2025-12-10 15:38:14 +00:00
Kleis Auke Wolthuizen
3609c61a22 Tests: fix JP2 suite with global libvips (#4477) 2025-11-15 10:55:58 +00:00
Jiralite
dc6820b49f TypeScript: tag deprecated constructor properties (#4474) 2025-11-10 16:41:22 +00:00
Sylvester Keil
f2a49d19c9 Fix invalid escape sequence (#4471) 2025-11-07 11:39:39 +00:00
Lovell Fuller
e062456868 Release v0.34.5 2025-11-06 14:06:31 +00:00
Lovell Fuller
6450c704a6 Prerelease v0.34.5-rc.1 2025-11-06 11:34:00 +00:00
Lovell Fuller
f7c95d1bf0 TypeScript: consolidate a few enum-like properties 2025-11-06 11:15:28 +00:00
Lovell Fuller
ef86a75560 Prerelease v0.34.5-rc.0 2025-11-05 15:41:31 +00:00
Lovell Fuller
6c1e840098 Use structured binding for tuples where possible 2025-11-05 15:30:52 +00:00
Lovell Fuller
e1628d8ef5 Simplify ICC processing when retaining input profiles #4468
Takes advantage of libvips' improved ICC handling
2025-11-04 15:06:49 +00:00
Lovell Fuller
4f9f8179a6 Linter: apply all recommended biome settings
Enforces previously-skipped useArrowFunction check
2025-11-04 09:41:45 +00:00
Lovell Fuller
09d5aa8cfa Docs: update internal and libvips doc links 2025-11-02 14:38:22 +00:00
Lovell Fuller
040b73ca74 Upgrade to libvips v8.17.3 2025-11-01 12:23:32 +00:00
Lovell Fuller
1f2f33d9a7 Ensure licensing headers are retained by code bundlers 2025-10-31 11:52:41 +00:00
Kleis Auke Wolthuizen
69b2c45615 Tests: migrate text suite to async (#4466) 2025-10-27 16:21:37 +00:00
Lovell Fuller
9e4e184132 Add experimental support for prebuilt linux-riscv64 binaries 2025-10-19 11:56:45 +01:00
Lovell Fuller
206eb4a89a Limit colour strings to 200 chars, helps reduce effect of potential ReDoS 2025-10-18 14:52:17 +01:00
Lovell Fuller
c1c16ed3e6 Improve error messaging when only warnings issued #4465 2025-10-17 14:57:38 +01:00
Lovell Fuller
b7fda60a85 Bump deps 2025-10-17 14:56:17 +01:00
Lovell Fuller
1bbee519aa Separate build script from install script #4458
The --build-from-source flag is now deprecated and will soon
be removed along with the need to define an install script.

This will remove a whole category of package manager
warnings about install scripts and "built" dependencies.

Most people don't need to build sharp from source, but for
those that do, a suitable method is now something like:

$ npm install package-that-depends-on-sharp
$ npm explore sharp -- npm run build
2025-10-07 16:11:54 +01:00
Lovell Fuller
2324d75f7f CI: Upgrade to macOS 15 (Sequoia) 2025-10-05 11:28:58 +01:00
Lovell Fuller
5e72ad95fa Docs: changelog entry for #4459 2025-09-30 10:54:00 +01:00
throwbi
6b922b30d5 Add support for BigTIFF output (#4459) 2025-09-30 09:41:02 +01:00
Lovell Fuller
54722dd582 Modernise C++ linter using new @cpplint/cli tooling 2025-09-22 14:59:52 +01:00
Lovell Fuller
adb6275ae9 Remove licensing checker/linter
This tool appears to no longer be maintained, but more
importantly there are far fewer production dependencies now
than when first introduced, and all are known/trusted.
2025-09-21 12:11:02 +01:00
Lovell Fuller
f2978651f0 Migrate from mocha to Node.js native test runner
Includes coverage reports when using Node.js 22 onwards
2025-09-21 12:03:27 +01:00
Kleis Auke Wolthuizen
c446d743a2 Docs: libvips manages its own thread pool (#4455) 2025-09-20 13:03:38 +01:00
Lovell Fuller
3498eb63e3 Docs: partially-revert 3009957, fix link to glibc malloc tunables 2025-09-20 10:47:43 +01:00
Lovell Fuller
3009957120 Docs: Add note about libvips thread pool sizing 2025-09-19 13:13:53 +01:00
Lovell Fuller
b36237ddcb Switch linter from semistandard to biome
Uses the recommended rules apart from complexity/useArrowFunction,
which would affect about 1700 lines of code with little benefit
right now. This is something that can be addressed over time.
2025-09-18 21:18:31 +01:00
Lovell Fuller
a0af662d78 CI: Separate platform-independent linter tasks
Run these before platform-specific build/testing tasks
2025-09-18 13:21:03 +01:00
146 changed files with 3814 additions and 3162 deletions

View File

@@ -1,17 +0,0 @@
freebsd_instance:
image_family: freebsd-15-0-snap
task:
name: FreeBSD
env:
IGNORE_OSVERSION: yes
skip_notifications: true
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
- pkg-config --modversion vips-cpp
install_script:
- npm install --build-from-source
test_script:
- npx mocha --no-config --spec=test/unit/io.js --timeout=30000

View File

@@ -7,6 +7,4 @@ indent_size = 2
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
max_line_length = 120
[*.md]
trim_trailing_whitespace = false

View File

@@ -6,8 +6,6 @@ Hello, thank you for your interest in helping!
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem. Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
If you're having installation problems, please include the output of running `npm install --verbose sharp`.
New bugs are assigned a `triage` label whilst under investigation. New bugs are assigned a `triage` label whilst under investigation.
## Submit a new feature request ## Submit a new feature request
@@ -16,7 +14,7 @@ If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exist
it's probably fastest to add a comment to it about your requirement. it's probably fastest to add a comment to it about your requirement.
Implementation is usually straightforward if libvips Implementation is usually straightforward if libvips
[already supports](https://www.libvips.org/API/current/func-list.html) [already supports](https://www.libvips.org/API/current/function-list.html)
the feature you need. the feature you need.
## Submit a Pull Request to fix a bug ## Submit a Pull Request to fix a bug
@@ -27,12 +25,11 @@ Please select the `main` branch as the destination for your Pull Request so your
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`. Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`. To test C++ changes, you can compile the module using `npm run build` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature ## Submit a Pull Request with a new feature
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature. Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Please also update the [TypeScript definitions](https://github.com/lovell/sharp/tree/main/lib/index.d.ts), along with the [type definition tests](https://github.com/lovell/sharp/tree/main/test/types/sharp.test-d.ts). Please also update the [TypeScript definitions](https://github.com/lovell/sharp/tree/main/lib/index.d.ts), along with the [type definition tests](https://github.com/lovell/sharp/tree/main/test/types/sharp.test-d.ts).
Where possible, the functional tests use gradient-based perceptual hashes Where possible, the functional tests use gradient-based perceptual hashes

View File

@@ -31,7 +31,7 @@ please open an issue against that package instead.
<!-- 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 with a version that satisfies `^18.17.0 || ^20.3.0 || >=21.0.0` - [ ] I am using Node.js with a version that satisfies `>=20.9.0`
- [ ] I am using Deno - [ ] I am using Deno
- [ ] I am using Bun - [ ] I am using Bun

View File

@@ -4,9 +4,23 @@ on:
- pull_request - pull_request
permissions: {} permissions: {}
jobs: jobs:
lint:
permissions:
contents: read
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "24"
- run: npm install --ignore-scripts
- run: npm run lint-cpp
- run: npm run lint-js
- run: npm run lint-types
build-native: build-native:
permissions: permissions:
contents: read contents: read
needs: lint
name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
container: ${{ matrix.container }} container: ${{ matrix.container }}
@@ -17,123 +31,124 @@ jobs:
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: linux-x64 platform: linux-x64
package: true package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20
platform: linux-x64
- os: ubuntu-24.04
container: rockylinux:8
nodejs_arch: x64
nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: linux-x64 platform: linux-x64
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:18-alpine3.17 container: rockylinux:8
nodejs_version_major: 18 nodejs_arch: x64
platform: linuxmusl-x64 nodejs_version: "^24"
package: true nodejs_version_major: 24
platform: linux-x64
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:20-alpine3.18 container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
platform: linuxmusl-x64 platform: linuxmusl-x64
package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:22-alpine3.20 container: node:22-alpine3.20
nodejs_version_major: 22 nodejs_version_major: 22
platform: linuxmusl-x64 platform: linuxmusl-x64
- os: ubuntu-24.04
container: node:24-alpine3.22
nodejs_version_major: 24
platform: linuxmusl-x64
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8 container: arm64v8/rockylinux:8
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: linux-arm64 platform: linux-arm64
package: true package: true
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8 container: arm64v8/rockylinux:8
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: linux-arm64 platform: linux-arm64
- os: macos-13 - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8
nodejs_arch: arm64
nodejs_version: "^24"
nodejs_version_major: 24
platform: linux-arm64
- os: macos-15-intel
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18
platform: darwin-x64
package: true
- os: macos-13
nodejs_arch: x64
nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: darwin-x64 platform: darwin-x64
- os: macos-13 package: true
- os: macos-15-intel
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^22.9.0" nodejs_version: "^22"
nodejs_version_major: 22 nodejs_version_major: 22
platform: darwin-x64 platform: darwin-x64
- os: macos-14 - os: macos-15-intel
nodejs_arch: x64
nodejs_version: "^24"
nodejs_version_major: 24
platform: darwin-x64
- os: macos-15
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18
platform: darwin-arm64
package: true
- os: macos-14
nodejs_arch: arm64
nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: darwin-arm64 platform: darwin-arm64
- os: macos-14 package: true
- os: macos-15
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^22.9.0" nodejs_version: "^22"
nodejs_version_major: 22 nodejs_version_major: 22
platform: darwin-arm64 platform: darwin-arm64
- os: macos-15
nodejs_arch: arm64
nodejs_version: "^24"
nodejs_version_major: 24
platform: darwin-arm64
- os: windows-2022 - os: windows-2022
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10 nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: win32-ia32 platform: win32-ia32
package: true package: true
- os: windows-2022
nodejs_arch: x86
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: win32-ia32
- os: windows-2022
nodejs_arch: x86
nodejs_version: "^22.9.0"
nodejs_version_major: 22
platform: win32-ia32
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: win32-x64 platform: win32-x64
package: true package: true
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: win32-x64 platform: win32-x64
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^22.9.0" nodejs_version: "^24"
nodejs_version_major: 22 nodejs_version_major: 24
platform: win32-x64 platform: win32-x64
- os: windows-11-arm - os: windows-11-arm
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^20.9.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: win32-arm64 platform: win32-arm64
package: true package: true
- os: windows-11-arm - os: windows-11-arm
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^22.9.0" nodejs_version: "^22"
nodejs_version_major: 22 nodejs_version_major: 22
platform: win32-arm64 platform: win32-arm64
- os: windows-11-arm
nodejs_arch: arm64
nodejs_version: "^24"
nodejs_version_major: 24
platform: win32-arm64
steps: steps:
- name: Dependencies (Rocky Linux glibc) - name: Dependencies (Rocky Linux glibc)
if: contains(matrix.container, 'rockylinux') if: contains(matrix.container, 'rockylinux')
@@ -145,24 +160,22 @@ jobs:
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@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
- name: Dependencies (Node.js) - name: Dependencies (Node.js)
if: "!contains(matrix.platform, 'linuxmusl')" if: "!contains(matrix.platform, 'linuxmusl')"
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: ${{ matrix.nodejs_version }} node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }} architecture: ${{ matrix.nodejs_arch }}
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install - run: npm install
run: npm install --build-from-source - run: npm run build
- name: Test - run: npm run test-unit
run: npm test - if: matrix.package
- name: Populate npm package
if: matrix.package
run: npm run package-from-local-build run: npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
if: matrix.package if: matrix.package
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
@@ -172,22 +185,25 @@ jobs:
build-linuxmusl-arm64: build-linuxmusl-arm64:
permissions: permissions:
contents: read contents: read
needs: lint
name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
container: container:
image: ${{ matrix.container }} image: ${{ matrix.container }}
volumes: volumes:
- /opt:/opt:rw,rshared - /opt:/opt:rw,rshared
- /opt:/__e/node20:ro,rshared - /opt:/__e/node24:ro,rshared
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- container: node:18-alpine3.17 - container: node:20-alpine3.20
nodejs_version_major: 18
package: true
- container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
package: true
- container: node:22-alpine3.20
nodejs_version_major: 22
- container: node:24-alpine3.22
nodejs_version_major: 24
steps: steps:
- name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757 - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757
shell: sh shell: sh
@@ -198,15 +214,13 @@ jobs:
ln -s /usr/bin/node /opt/bin/node ln -s /usr/bin/node /opt/bin/node
- name: Dependencies - name: Dependencies
run: apk add build-base git python3 font-noto --update-cache run: apk add build-base git python3 font-noto --update-cache
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Install - run: npm install
run: npm install --build-from-source - run: npm run build
- name: Test - run: npm run test-unit
run: npm test - if: matrix.package
- name: Populate npm package
if: matrix.package
run: npm run package-from-local-build run: npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
if: matrix.package if: matrix.package
with: with:
name: linuxmusl-arm64 name: linuxmusl-arm64
@@ -216,6 +230,7 @@ jobs:
build-qemu: build-qemu:
permissions: permissions:
contents: read contents: read
needs: lint
name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]" name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
strategy: strategy:
@@ -223,63 +238,93 @@ jobs:
matrix: matrix:
include: include:
- platform: linux-arm - platform: linux-arm
distro: bullseye base_image: "balenalib/rpi-raspbian:bullseye"
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: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-s390x - platform: linux-s390x
distro: bookworm base_image: "--platform=linux/s390x s390x/debian:bookworm"
run_on_arch: s390x
nodejs_arch: s390x nodejs_arch: s390x
nodejs_hostname: nodejs.org nodejs_hostname: nodejs.org
nodejs_version: "18.17.0" nodejs_version: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-ppc64 - platform: linux-ppc64
distro: bookworm base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm"
run_on_arch: ppc64le
nodejs_arch: ppc64le nodejs_arch: ppc64le
nodejs_hostname: nodejs.org nodejs_hostname: nodejs.org
nodejs_version: "18.17.0" nodejs_version: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-riscv64
base_image: "--platform=linux/riscv64 riscv64/debian:trixie"
compiler_flags: "-march=rv64gc"
nodejs_arch: riscv64
nodejs_hostname: unofficial-builds.nodejs.org
nodejs_version: "20.19.5"
nodejs_version_major: 20
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
with: with:
arch: ${{ matrix.run_on_arch }} arch: none
distro: ${{ matrix.distro }} distro: none
base_image: ${{ matrix.base_image }}
env: |
CFLAGS: "${{ matrix.compiler_flags }}"
CXXFLAGS: "${{ matrix.compiler_flags }}"
run: | run: |
apt-get update apt-get update
apt-get install -y curl g++ git libatomic1 make python3 xz-utils apt-get install -y curl g++ git libatomic1 make python3 xz-utils
mkdir /opt/nodejs mkdir /opt/nodejs
curl --silent https://${{ matrix.nodejs_hostname }}/download/release/v${{ matrix.nodejs_version}}/node-v${{ matrix.nodejs_version}}-linux-${{ matrix.nodejs_arch }}.tar.xz | tar xJC /opt/nodejs --strip-components=1 curl --silent https://${{ matrix.nodejs_hostname }}/download/release/v${{ matrix.nodejs_version}}/node-v${{ matrix.nodejs_version}}-linux-${{ matrix.nodejs_arch }}.tar.xz | tar xJC /opt/nodejs --strip-components=1
export PATH=$PATH:/opt/nodejs/bin export PATH=$PATH:/opt/nodejs/bin
npm install --build-from-source npm install
npx mocha --no-config --spec=test/unit/io.js --timeout=30000 npm run build
node --test test/unit/io.js
npm run package-from-local-build npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
path: npm/${{ matrix.platform }} path: npm/${{ matrix.platform }}
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
build-freebsd:
permissions:
contents: read
needs: lint
name: "build-freebsd"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: vmactions/freebsd-vm@v1
continue-on-error: true
with:
prepare: |
pkg update -f
pkg upgrade -y
pkg install -y devel/git devel/pkgconf graphics/vips www/node22 www/npm
pkg-config --modversion vips-cpp
run: |
npm install
npm run build
node --test test/unit/io.js
build-emscripten: build-emscripten:
permissions: permissions:
contents: read contents: read
needs: lint
name: "build-wasm32 [package]" name: "build-wasm32 [package]"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: "emscripten/emsdk:4.0.14" container: "emscripten/emsdk:4.0.21"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Dependencies - name: Dependencies
run: apt-get update && apt-get install -y pkg-config run: apt-get update && apt-get install -y pkg-config
- name: Dependencies (Node.js) - name: Dependencies (Node.js)
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: "20" node-version: "20"
- name: Install - run: npm install
run: emmake npm install --build-from-source - run: emmake npm run build
- name: Verify emscripten versions match - name: Verify emscripten versions match
run: | run: |
EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten") EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten")
@@ -287,11 +332,9 @@ jobs:
echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS" echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS"
echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP" echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP"
test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
- name: Test - run: emmake npm run test-unit
run: emmake npm test - run: emmake npm run package-from-local-build
- name: Populate npm package - uses: actions/upload-artifact@v6
run: emmake npm run package-from-local-build
- uses: actions/upload-artifact@v4
with: with:
name: wasm32 name: wasm32
path: npm/wasm32 path: npm/wasm32
@@ -309,12 +352,12 @@ jobs:
- build-emscripten - build-emscripten
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v7
with: with:
path: npm path: npm
- name: Create npm workspace tarball - name: Create npm workspace tarball
run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js . run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js .
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: '24' node-version: '24'
- name: Create release notes - name: Create release notes

View File

@@ -43,30 +43,30 @@ jobs:
runtime: bun runtime: bun
- name: darwin-x64-node-npm - name: darwin-x64-node-npm
runs-on: macos-13 runs-on: macos-15-intel
runtime: node runtime: node
package-manager: npm package-manager: npm
- name: darwin-x64-node-pnpm - name: darwin-x64-node-pnpm
runs-on: macos-13 runs-on: macos-15-intel
runtime: node runtime: node
package-manager: pnpm package-manager: pnpm
- name: darwin-x64-node-yarn - name: darwin-x64-node-yarn
runs-on: macos-13 runs-on: macos-15-intel
runtime: node runtime: node
package-manager: yarn package-manager: yarn
- name: darwin-x64-node-yarn-pnp - name: darwin-x64-node-yarn-pnp
runs-on: macos-13 runs-on: macos-15-intel
runtime: node runtime: node
package-manager: yarn-pnp package-manager: yarn-pnp
- name: darwin-x64-node-yarn-v1 - name: darwin-x64-node-yarn-v1
runs-on: macos-13 runs-on: macos-15-intel
runtime: node runtime: node
package-manager: yarn-v1 package-manager: yarn-v1
- name: darwin-x64-deno - name: darwin-x64-deno
runs-on: macos-13 runs-on: macos-15-intel
runtime: deno runtime: deno
- name: darwin-x64-bun - name: darwin-x64-bun
runs-on: macos-13 runs-on: macos-15-intel
runtime: bun runtime: bun
- name: win32-x64-node-npm - name: win32-x64-node-npm
@@ -96,7 +96,7 @@ jobs:
steps: steps:
- name: Install Node.js - name: Install Node.js
if: ${{ matrix.runtime == 'node' }} if: ${{ matrix.runtime == 'node' }}
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version: 20 node-version: 20
- name: Install pnpm - name: Install pnpm

2
.gitignore vendored
View File

@@ -1,7 +1,6 @@
src/build src/build
src/node_modules src/node_modules
node_modules node_modules
/coverage
npm/*/* npm/*/*
!npm/*/package.json !npm/*/package.json
test/bench/node_modules test/bench/node_modules
@@ -9,7 +8,6 @@ test/fixtures/output*
test/fixtures/vips-properties.xml test/fixtures/vips-properties.xml
test/leak/libvips.supp test/leak/libvips.supp
.DS_Store .DS_Store
.nyc_output
.vscode/ .vscode/
package-lock.json package-lock.json
.idea .idea

View File

@@ -1,7 +0,0 @@
{
"parallel": true,
"slow": 1000,
"timeout": 30000,
"require": "./test/beforeEach.js",
"spec": "./test/unit/*.js"
}

View File

@@ -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 or >= 20.3.0), Deno and Bun. Node.js (>= 20.9.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

26
biome.json Normal file
View File

@@ -0,0 +1,26 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": false,
"useEditorconfig": true
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
}
}

View File

@@ -1,6 +1,6 @@
// @ts-check // @ts-check
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight'; import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';
import starlightAutoSidebar from 'starlight-auto-sidebar'; import starlightAutoSidebar from 'starlight-auto-sidebar';
import { version } from '../package.json'; import { version } from '../package.json';

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';

View File

@@ -11,8 +11,9 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/starlight": "^0.35.2", "@astrojs/starlight": "^0.37.1",
"astro": "^5.13.5", "astro": "^5.16.6",
"starlight-auto-sidebar": "^0.1.2" "jsdoc-to-markdown": "^9.1.3",
"starlight-auto-sidebar": "^0.1.4"
} }
} }

View File

@@ -326,3 +326,6 @@ GitHub: https://github.com/tpatel
Name: Maël Nison Name: Maël Nison
GitHub: https://github.com/arcanis GitHub: https://github.com/arcanis
Name: Dmytro Tiapukhin
GitHub: https://github.com/eddienubes

View File

@@ -8,7 +8,7 @@ title: Channel manipulation
Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel. Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel.
See also [flatten](/api-operation#flatten). See also [flatten](/api-operation/#flatten).
**Example** **Example**

View File

@@ -80,7 +80,7 @@ as defined by [toColourspace](#tocolourspace).
| Param | Type | Description | | Param | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| [colourspace] | <code>string</code> | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) | | [colourspace] | <code>string</code> | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://www.libvips.org/API/current/enum.Interpretation.html) |
**Example** **Example**
```js ```js
@@ -123,7 +123,7 @@ By default output image will be web-friendly sRGB, with additional channels inte
| Param | Type | Description | | Param | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| [colourspace] | <code>string</code> | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) | | [colourspace] | <code>string</code> | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html) |
**Example** **Example**
```js ```js

View File

@@ -21,7 +21,7 @@ The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
`hard-light`, `soft-light`, `difference`, `exclusion`. `hard-light`, `soft-light`, `difference`, `exclusion`.
More information about blend modes can be found at More information about blend modes can be found at
https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode https://www.libvips.org/API/current/enum.BlendMode.html
and https://www.cairographics.org/operators/ and https://www.cairographics.org/operators/
@@ -64,8 +64,8 @@ and https://www.cairographics.org/operators/
| [images[].raw.height] | <code>Number</code> | | | | [images[].raw.height] | <code>Number</code> | | |
| [images[].raw.channels] | <code>Number</code> | | | | [images[].raw.channels] | <code>Number</code> | | |
| [images[].animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image. | | [images[].animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image. |
| [images[].failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | @see [constructor parameters](/api-constructor#parameters) | | [images[].failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | @see [constructor parameters](/api-constructor/) |
| [images[].limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | @see [constructor parameters](/api-constructor#parameters) | | [images[].limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | @see [constructor parameters](/api-constructor/) |
**Example** **Example**
```js ```js

View File

@@ -13,17 +13,17 @@ It does not take into consideration any operations to be applied to the output i
such as resize or rotate. such as resize or rotate.
Dimensions in the response will respect the `page` and `pages` properties of the Dimensions in the response will respect the `page` and `pages` properties of the
[constructor parameters](/api-constructor#parameters). [constructor parameters](/api-constructor/).
A `Promise` is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
- `size`: Total size of image in bytes, for Stream and Buffer input only - `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation) - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html)
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat) - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/enum.BandFormat.html)
- `density`: Number of pixels per inch (DPI), if present - `density`: Number of pixels per inch (DPI), if present
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
@@ -50,6 +50,7 @@ A `Promise` is returned when `callback` is not provided.
- `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. - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
- `gainMap.image`: HDR gain map, if present, as compressed JPEG image.

View File

@@ -170,7 +170,7 @@ inputStream
## sharpen ## sharpen
> sharpen([options], [flat], [jagged]) ⇒ <code>Sharp</code> > sharpen([options]) ⇒ <code>Sharp</code>
Sharpen the image. Sharpen the image.
@@ -179,7 +179,7 @@ When used without parameters, performs a fast, mild sharpen of the output image.
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available. Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen) operation. See [libvips sharpen](https://www.libvips.org/API/current/method.Image.sharpen.html) operation.
**Throws**: **Throws**:
@@ -189,15 +189,13 @@ See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.ht
| Param | Type | Default | Description | | Param | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [options] | <code>Object</code> \| <code>number</code> | | if present, is an Object with attributes | | [options] | <code>Object</code> | | if present, is an Object with attributes |
| [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 | | [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 |
| [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 | | [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 |
| [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 | | [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 |
| [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 | | [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 |
| [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 | | [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 |
| [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 | | [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 |
| [flat] | <code>number</code> | | (deprecated) see `options.m1`. |
| [jagged] | <code>number</code> | | (deprecated) see `options.m2`. |
**Example** **Example**
```js ```js
@@ -360,8 +358,6 @@ with all white pixel values made fully transparent.
Existing alpha channel values for non-white pixels remain unchanged. Existing alpha channel values for non-white pixels remain unchanged.
This feature is experimental and the API may change.
**Since**: 0.32.1 **Since**: 0.32.1
**Example** **Example**

View File

@@ -117,6 +117,38 @@ await sharp(pixelArray, { raw: { width, height, channels } })
``` ```
## toUint8Array
> toUint8Array() ⇒ <code>Promise.&lt;{data: Uint8Array, info: Object}&gt;</code>
Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
Use [toFormat](#toformat) or one of the format-specific functions such as [jpeg](#jpeg), [png](#png) etc. to set the output format.
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [keepExif](#keepexif) and similar methods for control over this.
Resolves with an `Object` containing:
- `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
- `info` contains properties relating to the output image such as `width` and `height`.
**Since**: v0.35.0
**Example**
```js
const { data, info } = await sharp(input).toUint8Array();
```
**Example**
```js
const { data } = await sharp(input)
.avif()
.toUint8Array();
const base64String = data.toBase64();
```
## keepExif ## keepExif
> keepExif() ⇒ <code>Sharp</code> > keepExif() ⇒ <code>Sharp</code>
@@ -201,7 +233,7 @@ const dataWithMergedExif = await sharp(inputWithExif)
Keep ICC profile from the input image in the output image. 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. When input and output colour spaces differ, use with [toColourspace](/api-colour/#tocolourspace) and optionally [pipelineColourspace](/api-colour/#pipelinecolourspace).
**Since**: 0.33.0 **Since**: 0.33.0
@@ -211,6 +243,14 @@ const outputWithIccProfile = await sharp(inputWithIccProfile)
.keepIccProfile() .keepIccProfile()
.toBuffer(); .toBuffer();
``` ```
**Example**
```js
const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile)
.pipelineColourspace('cmyk')
.toColourspace('cmyk')
.keepIccProfile()
.toBuffer();
```
## withIccProfile ## withIccProfile
@@ -242,6 +282,27 @@ const outputWithP3 = await sharp(input)
``` ```
## withGainMap
> withGainMap() ⇒ <code>Sharp</code>
If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
The input gain map is discarded.
If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
JPEG output options other than `quality` are ignored.
This feature is experimental and the API may change.
**Since**: 0.35.0
**Example**
```js
const outputWithGainMap = await sharp(inputWithGainMap)
.withGainMap()
.toBuffer();
```
## keepXmp ## keepXmp
> keepXmp() ⇒ <code>Sharp</code> > keepXmp() ⇒ <code>Sharp</code>
@@ -430,7 +491,7 @@ 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 For 16 bits per pixel output, convert to `rgb16` via
[toColourspace](/api-colour#tocolourspace). [toColourspace](/api-colour/#tocolourspace).
**Throws**: **Throws**:
@@ -502,6 +563,7 @@ Use these WebP options for output image.
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) | | [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
| [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) | | [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) | | [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
| [options.exact] | <code>boolean</code> | <code>false</code> | preserve the colour data in transparent pixels |
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format | | [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
**Example** **Example**
@@ -589,7 +651,7 @@ Use these JP2 options for output image.
Requires libvips compiled with support for OpenJPEG. Requires libvips compiled with support for OpenJPEG.
The prebuilt binaries do not include this - see The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips). [installing a custom libvips](/install#custom-libvips).
**Throws**: **Throws**:
@@ -646,6 +708,7 @@ instead of providing `xres` and `yres` in pixels/mm.
| [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 | | [options.quality] | <code>number</code> | <code>80</code> | quality, integer 1-100 |
| [options.force] | <code>boolean</code> | <code>true</code> | force TIFF output, otherwise attempt to use input format | | [options.force] | <code>boolean</code> | <code>true</code> | force TIFF output, otherwise attempt to use input format |
| [options.compression] | <code>string</code> | <code>&quot;&#x27;jpeg&#x27;&quot;</code> | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k | | [options.compression] | <code>string</code> | <code>&quot;&#x27;jpeg&#x27;&quot;</code> | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k |
| [options.bigtiff] | <code>boolean</code> | <code>false</code> | use BigTIFF variant (has no effect when compression is none) |
| [options.predictor] | <code>string</code> | <code>&quot;&#x27;horizontal&#x27;&quot;</code> | compression predictor options: none, horizontal, float | | [options.predictor] | <code>string</code> | <code>&quot;&#x27;horizontal&#x27;&quot;</code> | compression predictor options: none, horizontal, float |
| [options.pyramid] | <code>boolean</code> | <code>false</code> | write an image pyramid | | [options.pyramid] | <code>boolean</code> | <code>false</code> | write an image pyramid |
| [options.tile] | <code>boolean</code> | <code>false</code> | write a tiled tiff | | [options.tile] | <code>boolean</code> | <code>false</code> | write a tiled tiff |
@@ -678,8 +741,7 @@ 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. Prebuilt binaries support a bitdepth of 8 only.
This feature is experimental on the Windows ARM64 platform When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
and requires a CPU with ARM64v8.4 or later.
**Throws**: **Throws**:
@@ -696,6 +758,7 @@ and requires a CPU with ARM64v8.4 or later.
| [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>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling | | [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</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 | | [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
| [options.tune] | <code>string</code> | <code>&quot;&#x27;iq&#x27;&quot;</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr' |
**Example** **Example**
```js ```js
@@ -735,6 +798,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
| [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>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling | | [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</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 | | [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
| [options.tune] | <code>string</code> | <code>&quot;&#x27;ssim&#x27;&quot;</code> | tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq' |
**Example** **Example**
```js ```js
@@ -753,7 +817,7 @@ This feature is experimental, please do not use in production systems.
Requires libvips compiled with support for libjxl. Requires libvips compiled with support for libjxl.
The prebuilt binaries do not include this - see The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips). [installing a custom libvips](/install/#custom-libvips).
**Throws**: **Throws**:

View File

@@ -284,6 +284,7 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
| [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;&#x27;top-left pixel&#x27;&quot;</code> | Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. | | [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;&#x27;top-left pixel&#x27;&quot;</code> | Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. |
| [options.threshold] | <code>number</code> | <code>10</code> | Allowed difference from the above colour, a positive number. | | [options.threshold] | <code>number</code> | <code>10</code> | Allowed difference from the above colour, a positive number. |
| [options.lineArt] | <code>boolean</code> | <code>false</code> | Does the input more closely resemble line art (e.g. vector) rather than being photographic? | | [options.lineArt] | <code>boolean</code> | <code>false</code> | Does the input more closely resemble line art (e.g. vector) rather than being photographic? |
| [options.margin] | <code>number</code> | <code>0</code> | Leave a margin around trimmed content, value is in pixels. |
**Example** **Example**
```js ```js
@@ -320,4 +321,13 @@ const output = await sharp(input)
threshold: 42, threshold: 42,
}) })
.toBuffer(); .toBuffer();
```
**Example**
```js
// Trim image leaving (up to) a 10 pixel margin around the trimmed content.
const output = await sharp(input)
.trim({
margin: 10
})
.toBuffer();
``` ```

View File

@@ -114,7 +114,7 @@ e.g. libaom manages its own 4 threads when encoding AVIF images,
and these are independent of the value set here. and these are independent of the value set here.
:::note :::note
Further [control over performance](/performance) is available. Further [control over performance](/performance/) is available.
::: :::

View File

@@ -0,0 +1,21 @@
---
title: v0.34.5 - 6th November 2025
slug: changelog/v0.34.5
---
* Upgrade to libvips v8.17.3 for upstream bug fixes.
* Add experimental support for prebuilt Linux RISC-V 64-bit binaries.
* Support building from source with npm v12+, deprecate `--build-from-source` flag.
[#4458](https://github.com/lovell/sharp/issues/4458)
* Add support for BigTIFF output.
[#4459](https://github.com/lovell/sharp/pull/4459)
[@throwbi](https://github.com/throwbi)
* Improve error messaging when only warnings issued.
[#4465](https://github.com/lovell/sharp/issues/4465)
* Simplify ICC processing when retaining input profiles.
[#4468](https://github.com/lovell/sharp/issues/4468)

View File

@@ -0,0 +1,41 @@
---
title: v0.35.0 - TBC
slug: changelog/v0.35.0
---
* Breaking: Drop support for Node.js 18, now requires Node.js >= 20.9.0.
* Breaking: Remove `install` script from `package.json` file.
Compiling from source is now opt-in via the `build` script.
* Breaking: AVIF output is now tuned using SSIMULACRA2-based `iq` quality metrics rather than `ssim`.
* Breaking: Remove deprecated `failOnError` constructor property.
* Breaking: Remove deprecated `paletteBitDepth` from `metadata` response.
* Breaking: Remove deprecated properties from `sharpen` operation.
* Breaking: Rename `format.jp2k` as `format.jp2` for API consistency.
* Upgrade to libvips v8.18.0 for upstream bug fixes.
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.
* Add AVIF/HEIF `tune` option for control over quality metrics.
[#4227](https://github.com/lovell/sharp/issues/4227)
* Add `withGainMap` to process HDR JPEG images with embedded gain maps.
[#4314](https://github.com/lovell/sharp/issues/4314)
* Add `toUint8Array` for output image as a `TypedArray` backed by a transferable `ArrayBuffer`.
[#4355](https://github.com/lovell/sharp/issues/4355)
* TypeScript: Ensure `FormatEnum` keys match reality.
[#4475](https://github.com/lovell/sharp/issues/4475)
* Add `margin` option to `trim` operation.
[#4480](https://github.com/lovell/sharp/issues/4480)
[@eddienubes](https://github.com/eddienubes)
* Add WebP `exact` option for control over transparent pixel colour values.

View File

@@ -10,7 +10,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 >= 20.9.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

View File

@@ -20,10 +20,6 @@ npm install sharp
pnpm add sharp pnpm add sharp
``` ```
When using `pnpm`, add `sharp` to
[ignoredBuiltDependencies](https://pnpm.io/settings#ignoredbuiltdependencies)
to silence warnings.
```sh frame="none" ```sh frame="none"
yarn add sharp yarn add sharp
``` ```
@@ -39,7 +35,7 @@ deno run --allow-env --allow-ffi --allow-read --allow-sys ...
## Prerequisites ## Prerequisites
* Node-API v9 compatible runtime e.g. Node.js ^18.17.0 or >=20.3.0. * Node-API v9 compatible runtime e.g. Node.js >= 20.9.0.
## Prebuilt binaries ## Prebuilt binaries
@@ -49,12 +45,13 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* macOS ARM64 * macOS ARM64
* Linux ARM (glibc >= 2.31) * Linux ARM (glibc >= 2.31)
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2) * Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
* Linux RISC-V 64-bit (glibc >= 2.41)
* Linux ppc64 (glibc >= 2.36) * Linux ppc64 (glibc >= 2.36)
* Linux s390x (glibc >= 2.36) * Linux s390x (glibc >= 2.36)
* 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 (deprecated, Node.js 20 only)
* Windows ARM64 (experimental, CPU with ARMv8.4 required for all features) * Windows ARM64 (CPU with ARMv8.4 required for all features)
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.
@@ -111,12 +108,13 @@ and on macOS when running Node.js under Rosetta.
## Building from source ## Building from source
This module will be compiled from source at `npm install` time when: ```sh frame="none"
npm install sharp
npm explore sharp -- npm run build
```
* a globally-installed libvips is detected, or The build process will search for a globally-installed libvips.
* when the `npm install --build-from-source` flag is used. This detection logic can be skipped by setting the
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_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or
`SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated) `SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated)
environment variables. environment variables.
@@ -127,21 +125,12 @@ Building from source requires:
* [node-addon-api](https://www.npmjs.com/package/node-addon-api) version 7+ * [node-addon-api](https://www.npmjs.com/package/node-addon-api) version 7+
* [node-gyp](https://github.com/nodejs/node-gyp#installation) version 9+ 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.
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:
```sh frame="none" ```sh frame="none"
npm install --save node-addon-api node-gyp npm install --save node-addon-api node-gyp
``` ```
When using `pnpm`, you may need to add `sharp` to
[onlyBuiltDependencies](https://pnpm.io/settings#onlybuiltdependencies)
to ensure the installation script can be run.
For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
can be used to configure the target environment.
## WebAssembly ## WebAssembly
Experimental support is provided for runtime environments that provide Experimental support is provided for runtime environments that provide
@@ -164,10 +153,8 @@ as well as the additional [building from source](#building-from-source) dependen
```sh frame="none" ```sh frame="none"
pkg install -y pkgconf vips pkg install -y pkgconf vips
``` npm install sharp
npm explore sharp -- npm run build
```sh frame="none"
cd /usr/ports/graphics/vips/ && make install clean
``` ```
## Linux memory allocator ## Linux memory allocator
@@ -201,6 +188,12 @@ and how to configure it.
Some package managers use symbolic links Some package managers use symbolic links
but AWS Lambda does not support these within deployment packages. but AWS Lambda does not support these within deployment packages.
An alternative approach is to use a well-maintained, third-party Lambda Layer:
- [cbschuld/sharp-aws-lambda-layer](https://github.com/cbschuld/sharp-aws-lambda-layer)
- [pH200/sharp-layer](https://github.com/pH200/sharp-layer)
- [zoellner/sharp-heic-lambda-layer](https://github.com/zoellner/sharp-heic-lambda-layer)
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.

View File

@@ -17,7 +17,8 @@ before the Node.js process starts to increase the thread pool size.
export UV_THREADPOOL_SIZE="$(lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l)" export UV_THREADPOOL_SIZE="$(lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l)"
``` ```
libvips uses a glib-managed thread pool to avoid the overhead of spawning new threads. libvips uses a shared thread pool to avoid the overhead of spawning new threads.
The size of this thread pool will grow on demand and shrink when idle.
The default number of threads used to concurrently process each image is the same as the number of CPU cores, The default number of threads used to concurrently process each image is the same as the number of CPU cores,
except when using glibc-based Linux without jemalloc, where the default is `1` to help reduce memory fragmentation. except when using glibc-based Linux without jemalloc, where the default is `1` to help reduce memory fragmentation.
@@ -25,7 +26,7 @@ except when using glibc-based Linux without jemalloc, where the default is `1` t
Use [`sharp.concurrency()`](/api-utility/#concurrency) to manage the number of threads per image. Use [`sharp.concurrency()`](/api-utility/#concurrency) to manage the number of threads per image.
To reduce memory fragmentation when using the default Linux glibc memory allocator, set the To reduce memory fragmentation when using the default Linux glibc memory allocator, set the
[`MALLOC_ARENA_MAX`](https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html) [`MALLOC_ARENA_MAX`](https://sourceware.org/glibc/manual/latest/html_node/Memory-Allocation-Tunables.html)
environment variable before the Node.js process starts to reduce the number of memory pools. environment variable before the Node.js process starts to reduce the number of memory pools.
```sh frame="none" ```sh frame="none"

38
install/build.js Normal file
View File

@@ -0,0 +1,38 @@
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const {
useGlobalLibvips,
globalLibvipsVersion,
log,
spawnRebuild,
} = require('../lib/libvips');
log('Building from source');
log('See https://sharp.pixelplumbing.com/install#building-from-source');
try {
const addonApi = require('node-addon-api');
log(`Found node-addon-api ${addonApi.version || ''}`);
} catch (_err) {
log('Please add node-addon-api to your dependencies');
process.exit(1);
}
try {
const gyp = require('node-gyp');
log(`Found node-gyp ${gyp().version}`);
} catch (_err) {
log('Please add node-gyp to your dependencies');
process.exit(1);
}
if (useGlobalLibvips(log)) {
log(`Found globally-installed libvips v${globalLibvipsVersion()}`);
}
const status = spawnRebuild();
if (status !== 0) {
process.exit(status);
}

View File

@@ -1,41 +0,0 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
try {
const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips');
const buildFromSource = (msg) => {
log(msg);
log('Attempting to build from source via node-gyp');
try {
const addonApi = require('node-addon-api');
log(`Found node-addon-api ${addonApi.version || ''}`);
} catch (err) {
log('Please add node-addon-api to your dependencies');
return;
}
try {
const gyp = require('node-gyp');
log(`Found node-gyp ${gyp().version}`);
} catch (err) {
log('Please add node-gyp to your dependencies');
return;
}
log('See https://sharp.pixelplumbing.com/install#building-from-source');
const status = spawnRebuild();
if (status !== 0) {
process.exit(status);
}
};
if (useGlobalLibvips(log)) {
buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
} else if (process.env.npm_config_build_from_source) {
buildFromSource('Detected --build-from-source flag');
}
} catch (err) {
const summary = err.message.split(/\n/).slice(0, 1);
console.log(`sharp: skipping install check: ${summary}`);
}

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const is = require('./is'); const is = require('./is');
@@ -18,7 +18,7 @@ const bool = {
/** /**
* Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel. * Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel.
* *
* See also {@link /api-operation#flatten|flatten}. * See also {@link /api-operation/#flatten flatten}.
* *
* @example * @example
* sharp('rgba.png') * sharp('rgba.png')
@@ -163,7 +163,7 @@ function bandbool (boolOp) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Public instance functions // Public instance functions
removeAlpha, removeAlpha,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const color = require('@img/colour'); const color = require('@img/colour');
const is = require('./is'); const is = require('./is');
@@ -69,7 +69,7 @@ function grayscale (grayscale) {
* *
* The input image will be converted to the provided colourspace at the start of the pipeline. * The input image will be converted to the provided colourspace at the start of the pipeline.
* 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}.
* *
* @since 0.29.0 * @since 0.29.0
* *
@@ -80,7 +80,7 @@ function grayscale (grayscale) {
* .toColourspace('srgb') * .toColourspace('srgb')
* .toFile('16bpc-pipeline-to-8bpc-output.png') * .toFile('16bpc-pipeline-to-8bpc-output.png')
* *
* @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) * @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://www.libvips.org/API/current/enum.Interpretation.html)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -112,7 +112,7 @@ function pipelineColorspace (colorspace) {
* .toColourspace('rgb16') * .toColourspace('rgb16')
* .toFile('16-bpp.png') * .toFile('16-bpp.png')
* *
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -141,7 +141,10 @@ function toColorspace (colorspace) {
* @throws {Error} Invalid value * @throws {Error} Invalid value
*/ */
function _getBackgroundColourOption (value) { function _getBackgroundColourOption (value) {
if (is.object(value) || is.string(value)) { if (
is.object(value) ||
(is.string(value) && value.length >= 3 && value.length <= 200)
) {
const colour = color(value); const colour = color(value);
return [ return [
colour.red(), colour.red(),
@@ -172,7 +175,7 @@ function _setBackgroundColourOption (key, value) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Public // Public
tint, tint,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const is = require('./is'); const is = require('./is');
@@ -56,7 +56,7 @@ const blend = {
* `hard-light`, `soft-light`, `difference`, `exclusion`. * `hard-light`, `soft-light`, `difference`, `exclusion`.
* *
* More information about blend modes can be found at * More information about blend modes can be found at
* https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode * https://www.libvips.org/API/current/enum.BlendMode.html
* and https://www.cairographics.org/operators/ * and https://www.cairographics.org/operators/
* *
* @since 0.22.0 * @since 0.22.0
@@ -123,8 +123,8 @@ const blend = {
* @param {Number} [images[].raw.height] * @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels] * @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image. * @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters} * @param {string} [images[].failOn='warning'] - @see {@link /api-constructor/ constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters} * @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor/ constructor parameters}
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -206,7 +206,7 @@ function composite (images) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Sharp.prototype.composite = composite; Sharp.prototype.composite = composite;
Sharp.blend = blend; Sharp.blend = blend;
}; };

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const util = require('node:util'); const util = require('node:util');
const stream = require('node:stream'); const stream = require('node:stream');
@@ -12,6 +12,10 @@ require('./sharp');
// Use NODE_DEBUG=sharp to enable libvips warnings // Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp'); const debuglog = util.debuglog('sharp');
const queueListener = (queueLength) => {
Sharp.queue.emit('change', queueLength);
};
/** /**
* Constructor factory to create an instance of `sharp`, to which further methods are chained. * Constructor factory to create an instance of `sharp`, to which further methods are chained.
* *
@@ -205,6 +209,7 @@ const debuglog = util.debuglog('sharp');
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
const Sharp = function (input, options) { const Sharp = function (input, options) {
// biome-ignore lint/complexity/noArguments: constructor factory
if (arguments.length === 1 && !is.defined(input)) { if (arguments.length === 1 && !is.defined(input)) {
throw new Error('Invalid input'); throw new Error('Invalid input');
} }
@@ -273,6 +278,7 @@ const Sharp = function (input, options) {
trimBackground: [], trimBackground: [],
trimThreshold: -1, trimThreshold: -1,
trimLineArt: false, trimLineArt: false,
trimMargin: 0,
dilateWidth: 0, dilateWidth: 0,
erodeWidth: 0, erodeWidth: 0,
gamma: 0, gamma: 0,
@@ -301,6 +307,7 @@ const Sharp = function (input, options) {
fileOut: '', fileOut: '',
formatOut: 'input', formatOut: 'input',
streamOut: false, streamOut: false,
typedArrayOut: false,
keepMetadata: 0, keepMetadata: 0,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataDensity: 0, withMetadataDensity: 0,
@@ -308,6 +315,7 @@ const Sharp = function (input, options) {
withExif: {}, withExif: {},
withExifMerge: true, withExifMerge: true,
withXmp: '', withXmp: '',
withGainMap: false,
resolveWithObject: false, resolveWithObject: false,
loop: -1, loop: -1,
delay: [], delay: [],
@@ -343,6 +351,7 @@ const Sharp = function (input, options) {
webpEffort: 4, webpEffort: 4,
webpMinSize: false, webpMinSize: false,
webpMixed: false, webpMixed: false,
webpExact: false,
gifBitdepth: 8, gifBitdepth: 8,
gifEffort: 7, gifEffort: 7,
gifDither: 1, gifDither: 1,
@@ -353,6 +362,7 @@ const Sharp = function (input, options) {
gifProgressive: false, gifProgressive: false,
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffBigtiff: false,
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false, tiffPyramid: false,
tiffMiniswhite: false, tiffMiniswhite: false,
@@ -369,6 +379,7 @@ const Sharp = function (input, options) {
heifEffort: 4, heifEffort: 4,
heifChromaSubsampling: '4:4:4', heifChromaSubsampling: '4:4:4',
heifBitdepth: 8, heifBitdepth: 8,
heifTune: 'ssim',
jxlDistance: 1, jxlDistance: 1,
jxlDecodingTier: 0, jxlDecodingTier: 0,
jxlEffort: 7, jxlEffort: 7,
@@ -396,9 +407,7 @@ const Sharp = function (input, options) {
debuglog(warning); debuglog(warning);
}, },
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener
Sharp.queue.emit('change', queueLength);
}
}; };
this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
return this; return this;

93
lib/index.d.ts vendored
View File

@@ -27,7 +27,7 @@
/// <reference types="node" /> /// <reference types="node" />
import { Duplex } from 'stream'; import type { Duplex } from 'node:stream';
//#region Constructor functions //#region Constructor functions
@@ -259,7 +259,6 @@ declare namespace sharp {
* Set the pipeline colourspace. * Set the pipeline colourspace.
* The input image will be converted to the provided colourspace at the start of the pipeline. * The input image will be converted to the provided colourspace at the start of the pipeline.
* All operations will use this colourspace before converting to the output colourspace, as defined by toColourspace. * All operations will use this colourspace before converting to the output colourspace, as defined by toColourspace.
* This feature is experimental and has not yet been fully-tested with all operations.
* *
* @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ... * @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ...
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -470,21 +469,6 @@ declare namespace sharp {
*/ */
sharpen(options?: SharpenOptions): Sharp; sharpen(options?: SharpenOptions): Sharp;
/**
* Sharpen the image.
* When used without parameters, performs a fast, mild sharpen of the output image.
* When a sigma is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
* @param sigma the sigma of the Gaussian mask, where sigma = 1 + radius / 2.
* @param flat the level of sharpening to apply to "flat" areas. (optional, default 1.0)
* @param jagged the level of sharpening to apply to "jagged" areas. (optional, default 2.0)
* @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations
*
* @deprecated Use the object parameter `sharpen({sigma, m1, m2, x1, y2, y3})` instead
*/
sharpen(sigma?: number, flat?: number, jagged?: number): Sharp;
/** /**
* Apply median filter. When used without parameters the default window is 3x3. * Apply median filter. When used without parameters the default window is 3x3.
* @param size square mask size: size x size (optional, default 3) * @param size square mask size: size x size (optional, default 3)
@@ -693,6 +677,13 @@ declare namespace sharp {
*/ */
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>; toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
/**
* Write output to a Uint8Array backed by a transferable ArrayBuffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and RAW output are supported.
* By default, the format will match the input image, except SVG input which becomes PNG output.
* @returns A promise that resolves with an object containing the Uint8Array data and an info object containing the output image format, size (bytes), width, height and channels
*/
toUint8Array(): Promise<{ data: Uint8Array; info: OutputInfo }>;
/** /**
* Keep all EXIF metadata from the input image in the output image. * Keep all EXIF metadata from the input image in the output image.
* EXIF metadata is unsupported for TIFF output. * EXIF metadata is unsupported for TIFF output.
@@ -849,7 +840,7 @@ declare namespace sharp {
* @returns A sharp instance that can be used to chain operations * @returns A sharp instance that can be used to chain operations
*/ */
toFormat( toFormat(
format: keyof FormatEnum | AvailableFormatInfo, format: keyof FormatEnum | AvailableFormatInfo | "avif",
options?: options?:
| OutputOptions | OutputOptions
| JpegOptions | JpegOptions
@@ -860,6 +851,7 @@ declare namespace sharp {
| JxlOptions | JxlOptions
| GifOptions | GifOptions
| Jp2Options | Jp2Options
| RawOptions
| TiffOptions, | TiffOptions,
): Sharp; ): Sharp;
@@ -902,7 +894,7 @@ declare namespace sharp {
* - sharp.gravity: north, northeast, east, southeast, south, southwest, west, northwest, center or centre. * - sharp.gravity: north, northeast, east, southeast, south, southwest, west, northwest, center or centre.
* - sharp.strategy: cover only, dynamically crop using either the entropy or attention strategy. Some of these values are based on the object-position CSS property. * - sharp.strategy: cover only, dynamically crop using either the entropy or attention strategy. Some of these values are based on the object-position CSS property.
* *
* The experimental strategy-based approach resizes so one dimension is at its target length then repeatedly ranks edge regions, * The strategy-based approach 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. * discarding the edge with the lowest score based on the selected strategy.
* - entropy: focus on the region with the highest Shannon entropy. * - entropy: focus on the region with the highest Shannon entropy.
* - 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.
@@ -991,14 +983,6 @@ declare namespace sharp {
* 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning') * 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
*/ */
failOn?: FailOnOptions | undefined; failOn?: FailOnOptions | undefined;
/**
* By default halt processing and raise an error when loading invalid images.
* Set this flag to false if you'd rather apply a "best effort" to decode images,
* even if the data is corrupt or invalid. (optional, default true)
*
* @deprecated Use `failOn` instead
*/
failOnError?: boolean | undefined;
/** /**
* Do not process input images where the number of pixels (width x height) exceeds this limit. * Do not process input images where the number of pixels (width x height) exceeds this limit.
* Assumes image dimensions contained in the input metadata can be trusted. * Assumes image dimensions contained in the input metadata can be trusted.
@@ -1027,11 +1011,11 @@ declare namespace sharp {
openSlide?: OpenSlideInputOptions | undefined; openSlide?: OpenSlideInputOptions | undefined;
/** JPEG 2000 specific input options */ /** JPEG 2000 specific input options */
jp2?: Jp2InputOptions | undefined; jp2?: Jp2InputOptions | undefined;
/** Deprecated: use tiff.subifd instead */ /** @deprecated Use {@link SharpOptions.tiff} instead */
subifd?: number | undefined; subifd?: number | undefined;
/** Deprecated: use pdf.background instead */ /** @deprecated Use {@link SharpOptions.pdf} instead */
pdfBackground?: Colour | Color | undefined; pdfBackground?: Colour | Color | undefined;
/** Deprecated: use openSlide.level instead */ /** @deprecated Use {@link SharpOptions.openSlide} instead */
level?: number | undefined; level?: number | undefined;
/** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */ /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
animated?: boolean | undefined; animated?: boolean | undefined;
@@ -1181,6 +1165,12 @@ declare namespace sharp {
'IFD3'?: ExifDir; 'IFD3'?: ExifDir;
} }
type HeifCompression = 'av1' | 'hevc';
type HeifTune = 'iq' | 'ssim' | 'psnr';
type Unit = 'inch' | 'cm';
interface WriteableMetadata { interface WriteableMetadata {
/** Number of pixels per inch (DPI) */ /** Number of pixels per inch (DPI) */
density?: number | undefined; density?: number | undefined;
@@ -1259,7 +1249,7 @@ declare namespace sharp {
/** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */ /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */
tifftagPhotoshop?: Buffer | undefined; tifftagPhotoshop?: Buffer | undefined;
/** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */ /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */
compression?: 'av1' | 'hevc'; compression?: HeifCompression | undefined;
/** Default background colour, if present, for PNG (bKGD) and GIF images */ /** Default background colour, if present, for PNG (bKGD) and GIF images */
background?: { r: number; g: number; b: number } | { gray: number }; background?: { r: number; g: number; b: number } | { gray: number };
/** Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide */ /** Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide */
@@ -1267,11 +1257,13 @@ declare namespace sharp {
/** Number of Sub Image File Directories in an OME-TIFF image */ /** Number of Sub Image File Directories in an OME-TIFF image */
subifds?: number | undefined; subifds?: number | undefined;
/** The unit of resolution (density) */ /** The unit of resolution (density) */
resolutionUnit?: 'inch' | 'cm' | undefined; resolutionUnit?: Unit | 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. */ /** Array of keyword/text pairs representing PNG text blocks, if present. */
comments?: CommentsMetadata[] | undefined; comments?: CommentsMetadata[] | undefined;
/** HDR gain map, if present */
gainMap?: GainMapMetadata | undefined;
} }
interface LevelMetadata { interface LevelMetadata {
@@ -1284,16 +1276,21 @@ declare namespace sharp {
text: string; text: string;
} }
interface GainMapMetadata {
/** JPEG image */
image: Buffer;
}
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[];
/** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */ /** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */
isOpaque: boolean; isOpaque: boolean;
/** Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental) */ /** Histogram-based estimation of greyscale entropy, discarding alpha channel if any */
entropy: number; entropy: number;
/** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental) */ /** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any */
sharpness: number; sharpness: number;
/** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental) */ /** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram */
dominant: { r: number; g: number; b: number }; dominant: { r: number; g: number; b: number };
} }
@@ -1399,11 +1396,13 @@ declare namespace sharp {
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */ /** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
effort?: number | undefined; effort?: number | undefined;
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */ /** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
minSize?: boolean; minSize?: boolean | undefined;
/** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */ /** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
mixed?: boolean; mixed?: boolean | undefined;
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */ /** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
preset?: keyof PresetEnum | undefined; preset?: keyof PresetEnum | undefined;
/** Preserve the colour data in transparent pixels (optional, default false) */
exact?: boolean | undefined;
} }
interface AvifOptions extends OutputOptions { interface AvifOptions extends OutputOptions {
@@ -1417,13 +1416,15 @@ declare namespace sharp {
chromaSubsampling?: string | undefined; chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined; bitdepth?: 8 | 10 | 12 | undefined;
/** Tune output for a quality metric, one of 'iq', 'ssim' or 'psnr' (optional, default 'iq') */
tune?: HeifTune | undefined;
} }
interface HeifOptions extends OutputOptions { interface HeifOptions extends OutputOptions {
/** quality, integer 1-100 (optional, default 50) */ /** quality, integer 1-100 (optional, default 50) */
quality?: number | undefined; quality?: number | undefined;
/** compression format: av1, hevc (optional, default 'av1') */ /** compression format: av1, hevc (optional, default 'av1') */
compression?: 'av1' | 'hevc' | undefined; compression?: HeifCompression | undefined;
/** use lossless compression (optional, default false) */ /** use lossless compression (optional, default false) */
lossless?: boolean | undefined; lossless?: boolean | undefined;
/** Level of CPU effort to reduce file size, between 0 (fastest) and 9 (slowest) (optional, default 4) */ /** Level of CPU effort to reduce file size, between 0 (fastest) and 9 (slowest) (optional, default 4) */
@@ -1432,6 +1433,8 @@ declare namespace sharp {
chromaSubsampling?: string | undefined; chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined; bitdepth?: 8 | 10 | 12 | undefined;
/** Tune output for a quality metric, one of 'ssim', 'psnr' or 'iq' (optional, default 'ssim') */
tune?: HeifTune | undefined;
} }
interface GifOptions extends OutputOptions, AnimationOptions { interface GifOptions extends OutputOptions, AnimationOptions {
@@ -1460,6 +1463,8 @@ declare namespace sharp {
quality?: number | undefined; quality?: number | undefined;
/** Compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default 'jpeg') */ /** Compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default 'jpeg') */
compression?: string | undefined; compression?: string | undefined;
/** Use BigTIFF variant (has no effect when compression is none) (optional, default false) */
bigtiff?: boolean | undefined;
/** Compression predictor options: none, horizontal, float (optional, default 'horizontal') */ /** Compression predictor options: none, horizontal, float (optional, default 'horizontal') */
predictor?: string | undefined; predictor?: string | undefined;
/** Write an image pyramid (optional, default false) */ /** Write an image pyramid (optional, default false) */
@@ -1479,7 +1484,7 @@ declare namespace sharp {
/** Write 1-bit images as miniswhite (optional, default false) */ /** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined; miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */ /** Resolution unit options: inch, cm (optional, default 'inch') */
resolutionUnit?: 'inch' | 'cm' | undefined; resolutionUnit?: Unit | undefined;
} }
interface PngOptions extends OutputOptions { interface PngOptions extends OutputOptions {
@@ -1601,10 +1606,12 @@ declare namespace sharp {
threshold?: number | undefined; threshold?: number | undefined;
/** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */ /** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
lineArt?: boolean | undefined; lineArt?: boolean | undefined;
/** Leave a margin around trimmed content, value is in pixels. (optional, default 0) */
margin?: number | undefined;
} }
interface RawOptions { interface RawOptions {
depth?: 'char' | 'uchar' | 'short' | 'ushort' | 'int' | 'uint' | 'float' | 'complex' | 'double' | 'dpcomplex'; depth?: keyof DepthEnum;
} }
/** 1 for grayscale, 2 for grayscale + alpha, 3 for sRGB, 4 for CMYK or RGBA */ /** 1 for grayscale, 2 for grayscale + alpha, 3 for sRGB, 4 for CMYK or RGBA */
@@ -1906,16 +1913,13 @@ declare namespace sharp {
} }
interface FormatEnum { interface FormatEnum {
avif: AvailableFormatInfo;
dcraw: AvailableFormatInfo; dcraw: AvailableFormatInfo;
dz: AvailableFormatInfo; dz: AvailableFormatInfo;
exr: AvailableFormatInfo; exr: AvailableFormatInfo;
fits: AvailableFormatInfo; fits: AvailableFormatInfo;
gif: AvailableFormatInfo; gif: AvailableFormatInfo;
heif: AvailableFormatInfo; heif: AvailableFormatInfo;
input: AvailableFormatInfo;
jpeg: AvailableFormatInfo; jpeg: AvailableFormatInfo;
jpg: AvailableFormatInfo;
jp2: AvailableFormatInfo; jp2: AvailableFormatInfo;
jxl: AvailableFormatInfo; jxl: AvailableFormatInfo;
magick: AvailableFormatInfo; magick: AvailableFormatInfo;
@@ -1927,8 +1931,7 @@ declare namespace sharp {
raw: AvailableFormatInfo; raw: AvailableFormatInfo;
svg: AvailableFormatInfo; svg: AvailableFormatInfo;
tiff: AvailableFormatInfo; tiff: AvailableFormatInfo;
tif: AvailableFormatInfo; vips: AvailableFormatInfo;
v: AvailableFormatInfo;
webp: AvailableFormatInfo; webp: AvailableFormatInfo;
} }

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const Sharp = require('./constructor'); const Sharp = require('./constructor');
require('./input')(Sharp); require('./input')(Sharp);

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const is = require('./is'); const is = require('./is');
const sharp = require('./sharp'); const sharp = require('./sharp');
@@ -30,7 +30,7 @@ const inputStreamParameters = [
// Format-specific // Format-specific
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff', 'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
// Deprecated // Deprecated
'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd' 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
]; ];
/** /**
@@ -54,7 +54,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = { const inputDescriptor = {
autoOrient: false, autoOrient: false,
failOn: 'warning', failOn: 'warning',
limitInputPixels: Math.pow(0x3FFF, 2), limitInputPixels: 0x3FFF ** 2,
ignoreIcc: false, ignoreIcc: false,
unlimited: false, unlimited: false,
sequentialRead: true sequentialRead: true
@@ -106,14 +106,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}`); }`);
} }
if (is.object(inputOptions)) { if (is.object(inputOptions)) {
// Deprecated: failOnError
if (is.defined(inputOptions.failOnError)) {
if (is.bool(inputOptions.failOnError)) {
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
} else {
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
}
}
// failOn // failOn
if (is.defined(inputOptions.failOn)) { if (is.defined(inputOptions.failOn)) {
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) { if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
@@ -150,7 +142,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.limitInputPixels)) { if (is.defined(inputOptions.limitInputPixels)) {
if (is.bool(inputOptions.limitInputPixels)) { if (is.bool(inputOptions.limitInputPixels)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
? Math.pow(0x3FFF, 2) ? 0x3FFF ** 2
: 0; : 0;
} else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) { } else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels; inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
@@ -513,7 +505,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
} else if (is.defined(inputOptions)) { } else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions); throw new Error(`Invalid input options ${inputOptions}`);
} }
return inputDescriptor; return inputDescriptor;
} }
@@ -525,10 +517,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* @param {string} encoding - unused * @param {string} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
function _write (chunk, encoding, callback) { function _write (chunk, _encoding, callback) {
/* istanbul ignore else */
if (Array.isArray(this.options.input.buffer)) { if (Array.isArray(this.options.input.buffer)) {
/* istanbul ignore else */
if (is.buffer(chunk)) { if (is.buffer(chunk)) {
if (this.options.input.buffer.length === 0) { if (this.options.input.buffer.length === 0) {
this.on('finish', () => { this.on('finish', () => {
@@ -572,17 +562,17 @@ function _isStreamInput () {
* such as resize or rotate. * such as resize or rotate.
* *
* Dimensions in the response will respect the `page` and `pages` properties of the * Dimensions in the response will respect the `page` and `pages` properties of the
* {@link /api-constructor#parameters|constructor parameters}. * {@link /api-constructor/ constructor parameters}.
* *
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
* *
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` * - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
* - `size`: Total size of image in bytes, for Stream and Buffer input only * - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation) * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat) * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/enum.BandFormat.html)
* - `density`: Number of pixels per inch (DPI), if present * - `density`: Number of pixels per inch (DPI), if present
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK * - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan * - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
@@ -609,6 +599,7 @@ function _isStreamInput () {
* - `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. * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
* - `gainMap.image`: HDR gain map, if present, as compressed JPEG image.
* *
* @example * @example
* const metadata = await sharp(input).metadata(); * const metadata = await sharp(input).metadata();
@@ -794,7 +785,7 @@ function stats (callback) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Private // Private
_inputOptionsFromObject, _inputOptionsFromObject,

View File

@@ -1,61 +1,49 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
/** /**
* Is this value defined and not null? * Is this value defined and not null?
* @private * @private
*/ */
const defined = function (val) { const defined = (val) => typeof val !== 'undefined' && val !== null;
return typeof val !== 'undefined' && val !== null;
};
/** /**
* Is this value an object? * Is this value an object?
* @private * @private
*/ */
const object = function (val) { const object = (val) => typeof val === 'object';
return typeof val === 'object';
};
/** /**
* Is this value a plain object? * Is this value a plain object?
* @private * @private
*/ */
const plainObject = function (val) { const plainObject = (val) => Object.prototype.toString.call(val) === '[object Object]';
return Object.prototype.toString.call(val) === '[object Object]';
};
/** /**
* Is this value a function? * Is this value a function?
* @private * @private
*/ */
const fn = function (val) { const fn = (val) => typeof val === 'function';
return typeof val === 'function';
};
/** /**
* Is this value a boolean? * Is this value a boolean?
* @private * @private
*/ */
const bool = function (val) { const bool = (val) => typeof val === 'boolean';
return typeof val === 'boolean';
};
/** /**
* Is this value a Buffer object? * Is this value a Buffer object?
* @private * @private
*/ */
const buffer = function (val) { const buffer = (val) => val instanceof Buffer;
return val instanceof Buffer;
};
/** /**
* Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray? * Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray?
* @private * @private
*/ */
const typedArray = function (val) { const typedArray = (val) => {
if (defined(val)) { if (defined(val)) {
switch (val.constructor) { switch (val.constructor) {
case Uint8Array: case Uint8Array:
@@ -78,49 +66,37 @@ const typedArray = function (val) {
* Is this value an ArrayBuffer object? * Is this value an ArrayBuffer object?
* @private * @private
*/ */
const arrayBuffer = function (val) { const arrayBuffer = (val) => val instanceof ArrayBuffer;
return val instanceof ArrayBuffer;
};
/** /**
* Is this value a non-empty string? * Is this value a non-empty string?
* @private * @private
*/ */
const string = function (val) { const string = (val) => typeof val === 'string' && val.length > 0;
return typeof val === 'string' && val.length > 0;
};
/** /**
* Is this value a real number? * Is this value a real number?
* @private * @private
*/ */
const number = function (val) { const number = (val) => typeof val === 'number' && !Number.isNaN(val);
return typeof val === 'number' && !Number.isNaN(val);
};
/** /**
* Is this value an integer? * Is this value an integer?
* @private * @private
*/ */
const integer = function (val) { const integer = (val) => Number.isInteger(val);
return Number.isInteger(val);
};
/** /**
* Is this value within an inclusive given range? * Is this value within an inclusive given range?
* @private * @private
*/ */
const inRange = function (val, min, max) { const inRange = (val, min, max) => val >= min && val <= max;
return val >= min && val <= max;
};
/** /**
* Is this value within the elements of an array? * Is this value within the elements of an array?
* @private * @private
*/ */
const inArray = function (val, list) { const inArray = (val, list) => list.includes(val);
return list.includes(val);
};
/** /**
* Create an Error with a message relating to an invalid parameter. * Create an Error with a message relating to an invalid parameter.
@@ -131,11 +107,9 @@ const inArray = function (val, list) {
* @returns {Error} Containing the formatted message. * @returns {Error} Containing the formatted message.
* @private * @private
*/ */
const invalidParameterError = function (name, expected, actual) { const invalidParameterError = (name, expected, actual) => new Error(
return new Error(
`Expected ${expected} for ${name} but received ${actual} of type ${typeof actual}` `Expected ${expected} for ${name} but received ${actual} of type ${typeof actual}`
); );
};
/** /**
* Ensures an Error from C++ contains a JS stack. * Ensures an Error from C++ contains a JS stack.
@@ -145,7 +119,7 @@ const invalidParameterError = function (name, expected, actual) {
* @returns {Error} Error with message and stack. * @returns {Error} Error with message and stack.
* @private * @private
*/ */
const nativeError = function (native, context) { const nativeError = (native, context) => {
context.message = native.message; context.message = native.message;
return context; return context;
}; };

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const { spawnSync } = require('node:child_process'); const { spawnSync } = require('node:child_process');
const { createHash } = require('node:crypto'); const { createHash } = require('node:crypto');
@@ -12,13 +12,13 @@ const detectLibc = require('detect-libc');
const { config, engines, optionalDependencies } = require('../package.json'); const { config, engines, optionalDependencies } = require('../package.json');
const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */ /* node:coverage ignore next */
config.libvips; const minimumLibvipsVersionLabelled = process.env.npm_package_config_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-ppc64', 'linux-s390x', 'linux-x64', 'linux-arm', 'linux-arm64', 'linux-ppc64', 'linux-riscv64', 'linux-s390x', 'linux-x64',
'linuxmusl-arm64', 'linuxmusl-x64', 'linuxmusl-arm64', 'linuxmusl-x64',
'win32-arm64', 'win32-ia32', 'win32-x64' 'win32-arm64', 'win32-ia32', 'win32-x64'
]; ];
@@ -36,17 +36,16 @@ const log = (item) => {
} }
}; };
/* istanbul ignore next */ /* node:coverage ignore next */
const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : ''; const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '';
const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`; const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`;
/* istanbul ignore next */
const buildPlatformArch = () => { const buildPlatformArch = () => {
/* node:coverage ignore next 3 */
if (isEmscripten()) { if (isEmscripten()) {
return 'wasm32'; return 'wasm32';
} }
/* 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;
const libc = typeof npm_config_libc === 'string' ? npm_config_libc : runtimeLibc(); const libc = typeof npm_config_libc === 'string' ? npm_config_libc : runtimeLibc();
return `${npm_config_platform || process.platform}${libc}-${npm_config_arch || process.arch}`; return `${npm_config_platform || process.platform}${libc}-${npm_config_arch || process.arch}`;
@@ -56,19 +55,19 @@ const buildSharpLibvipsIncludeDir = () => {
try { try {
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/include`); return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/include`);
} catch { } catch {
/* node:coverage ignore next 5 */
try { try {
return require('@img/sharp-libvips-dev/include'); return require('@img/sharp-libvips-dev/include');
} catch {} } catch {}
} }
/* istanbul ignore next */
return ''; return '';
}; };
const buildSharpLibvipsCPlusPlusDir = () => { const buildSharpLibvipsCPlusPlusDir = () => {
/* node:coverage ignore next 4 */
try { try {
return require('@img/sharp-libvips-dev/cplusplus'); return require('@img/sharp-libvips-dev/cplusplus');
} catch {} } catch {}
/* istanbul ignore next */
return ''; return '';
}; };
@@ -76,16 +75,17 @@ const buildSharpLibvipsLibDir = () => {
try { try {
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/lib`); return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/lib`);
} catch { } catch {
/* node:coverage ignore next 5 */
try { try {
return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`); return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
} catch {} } catch {}
} }
/* istanbul ignore next */
return ''; return '';
}; };
/* node:coverage disable */
const isUnsupportedNodeRuntime = () => { const isUnsupportedNodeRuntime = () => {
/* istanbul ignore next */
if (process.release?.name === 'node' && process.versions) { if (process.release?.name === 'node' && process.versions) {
if (!semverSatisfies(process.versions.node, engines.node)) { if (!semverSatisfies(process.versions.node, engines.node)) {
return { found: process.versions.node, expected: engines.node }; return { found: process.versions.node, expected: engines.node };
@@ -93,14 +93,12 @@ const isUnsupportedNodeRuntime = () => {
} }
}; };
/* istanbul ignore next */
const isEmscripten = () => { const isEmscripten = () => {
const { CC } = process.env; const { CC } = process.env;
return Boolean(CC && CC.endsWith('/emcc')); return Boolean(CC?.endsWith('/emcc'));
}; };
const isRosetta = () => { const isRosetta = () => {
/* istanbul ignore next */
if (process.platform === 'darwin' && process.arch === 'x64') { if (process.platform === 'darwin' && process.arch === 'x64') {
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout; const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
return (translated || '').trim() === 'sysctl.proc_translated: 1'; return (translated || '').trim() === 'sysctl.proc_translated: 1';
@@ -108,6 +106,8 @@ const isRosetta = () => {
return false; return false;
}; };
/* node:coverage enable */
const sha512 = (s) => createHash('sha512').update(s).digest('hex'); const sha512 = (s) => createHash('sha512').update(s).digest('hex');
const yarnLocator = () => { const yarnLocator = () => {
@@ -121,7 +121,8 @@ const yarnLocator = () => {
return ''; return '';
}; };
/* istanbul ignore next */ /* node:coverage disable */
const spawnRebuild = () => const spawnRebuild = () =>
spawnSync(`node-gyp rebuild --directory=src ${isEmscripten() ? '--nodedir=emscripten' : ''}`, { spawnSync(`node-gyp rebuild --directory=src ${isEmscripten() ? '--nodedir=emscripten' : ''}`, {
...spawnSyncOptions, ...spawnSyncOptions,
@@ -137,16 +138,17 @@ const globalLibvipsVersion = () => {
PKG_CONFIG_PATH: pkgConfigPath() PKG_CONFIG_PATH: pkgConfigPath()
} }
}).stdout; }).stdout;
/* istanbul ignore next */
return (globalLibvipsVersion || '').trim(); return (globalLibvipsVersion || '').trim();
} else { } else {
return ''; return '';
} }
}; };
/* istanbul ignore next */ /* node:coverage enable */
const pkgConfigPath = () => { const pkgConfigPath = () => {
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
/* node:coverage ignore next 4 */
const brewPkgConfigPath = spawnSync( const brewPkgConfigPath = spawnSync(
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2', 'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
spawnSyncOptions spawnSyncOptions
@@ -178,13 +180,13 @@ const useGlobalLibvips = (logger) => {
if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) { if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) {
return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS', logger); return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS', logger);
} }
/* istanbul ignore next */ /* node:coverage ignore next 3 */
if (isRosetta()) { if (isRosetta()) {
return skipSearch(false, 'Rosetta', logger); return skipSearch(false, 'Rosetta', logger);
} }
const globalVipsVersion = globalLibvipsVersion(); const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */ /* node:coverage ignore next */
semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion); return !!globalVipsVersion && semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
}; };
module.exports = { module.exports = {

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const is = require('./is'); const is = require('./is');
@@ -239,7 +239,7 @@ function affine (matrix, options) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available. * Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
* *
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation. * See {@link https://www.libvips.org/API/current/method.Image.sharpen.html libvips sharpen} operation.
* *
* @example * @example
* const data = await sharp(input).sharpen().toBuffer(); * const data = await sharp(input).sharpen().toBuffer();
@@ -259,45 +259,18 @@ function affine (matrix, options) {
* }) * })
* .toBuffer(); * .toBuffer();
* *
* @param {Object|number} [options] - if present, is an Object with attributes * @param {Object} [options] - if present, is an Object with attributes
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000 * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000 * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000 * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000 * @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000 * @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function sharpen (options, flat, jagged) { function sharpen (options) {
if (!is.defined(options)) { if (is.plainObject(options)) {
// No arguments: default to mild sharpen
this.options.sharpenSigma = -1;
} else if (is.bool(options)) {
// Deprecated boolean argument: apply mild sharpen?
this.options.sharpenSigma = options ? -1 : 0;
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
// Deprecated numeric argument: specific sigma
this.options.sharpenSigma = options;
// Deprecated control over flat areas
if (is.defined(flat)) {
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
this.options.sharpenM1 = flat;
} else {
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
}
}
// Deprecated control over jagged areas
if (is.defined(jagged)) {
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
this.options.sharpenM2 = jagged;
} else {
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
}
}
} else if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) { if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
this.options.sharpenSigma = options.sigma; this.options.sharpenSigma = options.sigma;
} else { } else {
@@ -339,7 +312,7 @@ function sharpen (options, flat, jagged) {
} }
} }
} else { } else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options); this.options.sharpenSigma = -1;
} }
return this; return this;
} }
@@ -485,7 +458,7 @@ function erode (width) {
/** /**
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel. * Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
* *
* See also {@link /api-channel#removealpha|removeAlpha}. * See also {@link /api-channel#removealpha removeAlpha}.
* *
* @example * @example
* await sharp(rgbaInput) * await sharp(rgbaInput)
@@ -510,8 +483,6 @@ function flatten (options) {
* *
* Existing alpha channel values for non-white pixels remain unchanged. * Existing alpha channel values for non-white pixels remain unchanged.
* *
* This feature is experimental and the API may change.
*
* @since 0.32.1 * @since 0.32.1
* *
* @example * @example
@@ -660,7 +631,7 @@ function normalize (options) {
/** /**
* Perform contrast limiting adaptive histogram equalization * Perform contrast limiting adaptive histogram equalization
* {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}. * {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE CLAHE}.
* *
* This will, in general, enhance the clarity of the image by bringing out darker details. * This will, in general, enhance the clarity of the image by bringing out darker details.
* *
@@ -742,9 +713,7 @@ function convolve (kernel) {
} }
// Default scale is sum of kernel values // Default scale is sum of kernel values
if (!is.integer(kernel.scale)) { if (!is.integer(kernel.scale)) {
kernel.scale = kernel.kernel.reduce(function (a, b) { kernel.scale = kernel.kernel.reduce((a, b) => a + b, 0);
return a + b;
}, 0);
} }
// Clip scale to a minimum value of 1 // Clip scale to a minimum value of 1
if (kernel.scale < 1) { if (kernel.scale < 1) {
@@ -989,7 +958,7 @@ function modulate (options) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
autoOrient, autoOrient,
rotate, rotate,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const path = require('node:path'); const path = require('node:path');
const is = require('./is'); const is = require('./is');
@@ -43,7 +43,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
* Note that raw pixel data is only supported for buffer output. * Note that raw pixel data is only supported for buffer output.
* *
* By default all metadata will be removed, which includes EXIF-based orientation. * By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link #withmetadata|withMetadata} for control over this. * See {@link #withmetadata withMetadata} for control over this.
* *
* The caller is responsible for ensuring directory structures and permissions exist. * The caller is responsible for ensuring directory structures and permissions exist.
* *
@@ -76,7 +76,7 @@ function toFile (fileOut, callback) {
err = new Error('Missing output file path'); err = new Error('Missing output file path');
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) { } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
err = new Error('Cannot use same file for input and output'); err = new Error('Cannot use same file for input and output');
} else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) { } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2.output.file) {
err = errJp2Save(); err = errJp2Save();
} }
if (err) { if (err) {
@@ -97,12 +97,12 @@ function toFile (fileOut, callback) {
* Write output to a Buffer. * Write output to a Buffer.
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported. * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
* *
* Use {@link #toformat|toFormat} or one of the format-specific functions such as {@link jpeg}, {@link png} etc. to set the output format. * Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
* *
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output. * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
* *
* By default all metadata will be removed, which includes EXIF-based orientation. * By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link #withmetadata|withMetadata} for control over this. * See {@link #withmetadata withMetadata} for control over this.
* *
* `callback`, if present, gets three arguments `(err, data, info)` where: * `callback`, if present, gets three arguments `(err, data, info)` where:
* - `err` is an error, if any. * - `err` is an error, if any.
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
return this._pipeline(is.fn(options) ? options : callback, stack); return this._pipeline(is.fn(options) ? options : callback, stack);
} }
/**
* Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
*
* Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
*
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link #keepexif keepExif} and similar methods for control over this.
*
* Resolves with an `Object` containing:
* - `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
* - `info` contains properties relating to the output image such as `width` and `height`.
*
* @since v0.35.0
*
* @example
* const { data, info } = await sharp(input).toUint8Array();
*
* @example
* const { data } = await sharp(input)
* .avif()
* .toUint8Array();
* const base64String = data.toBase64();
*
* @returns {Promise<{ data: Uint8Array, info: Object }>}
*/
function toUint8Array () {
this.options.resolveWithObject = true;
this.options.typedArrayOut = true;
const stack = Error();
return this._pipeline(null, stack);
}
/** /**
* Keep all EXIF metadata from the input image in the output image. * Keep all EXIF metadata from the input image in the output image.
* *
@@ -256,7 +291,7 @@ function withExifMerge (exif) {
/** /**
* Keep ICC profile from the input image in the output image. * 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. * When input and output colour spaces differ, use with {@link /api-colour/#tocolourspace toColourspace} and optionally {@link /api-colour/#pipelinecolourspace pipelineColourspace}.
* *
* @since 0.33.0 * @since 0.33.0
* *
@@ -265,6 +300,13 @@ function withExifMerge (exif) {
* .keepIccProfile() * .keepIccProfile()
* .toBuffer(); * .toBuffer();
* *
* @example
* const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile)
* .pipelineColourspace('cmyk')
* .toColourspace('cmyk')
* .keepIccProfile()
* .toBuffer();
*
* @returns {Sharp} * @returns {Sharp}
*/ */
function keepIccProfile () { function keepIccProfile () {
@@ -312,6 +354,30 @@ function withIccProfile (icc, options) {
return this; return this;
} }
/**
* If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
* The input gain map is discarded.
*
* If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
* JPEG output options other than `quality` are ignored.
*
* This feature is experimental and the API may change.
*
* @since 0.35.0
*
* @example
* const outputWithGainMap = await sharp(inputWithGainMap)
* .withGainMap()
* .toBuffer();
*
* @returns {Sharp}
*/
function withGainMap() {
this.options.withGainMap = true;
this.options.colourspace = 'scrgb';
return this;
}
/** /**
* Keep XMP metadata from the input image in the output image. * Keep XMP metadata from the input image in the output image.
* *
@@ -565,7 +631,7 @@ function jpeg (options) {
* 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 * For 16 bits per pixel output, convert to `rgb16` via
* {@link /api-colour#tocolourspace|toColourspace}. * {@link /api-colour/#tocolourspace toColourspace}.
* *
* @example * @example
* // Convert any input to full colour PNG output * // Convert any input to full colour PNG output
@@ -683,6 +749,7 @@ function png (options) {
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds) * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow) * @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow) * @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
* @param {boolean} [options.exact=false] - preserve the colour data in transparent pixels
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@@ -735,6 +802,9 @@ function webp (options) {
if (is.defined(options.mixed)) { if (is.defined(options.mixed)) {
this._setBooleanOption('webpMixed', options.mixed); this._setBooleanOption('webpMixed', options.mixed);
} }
if (is.defined(options.exact)) {
this._setBooleanOption('webpExact', options.exact);
}
} }
trySetAnimationOptions(options, this.options); trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
@@ -845,13 +915,12 @@ function gif (options) {
return this._updateFormatOut('gif', options); return this._updateFormatOut('gif', options);
} }
/* istanbul ignore next */
/** /**
* Use these JP2 options for output image. * Use these JP2 options for output image.
* *
* Requires libvips compiled with support for OpenJPEG. * Requires libvips compiled with support for OpenJPEG.
* The prebuilt binaries do not include this - see * The prebuilt binaries do not include this - see
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}. * {@link /install#custom-libvips installing a custom libvips}.
* *
* @example * @example
* // Convert any input to lossless JP2 output * // Convert any input to lossless JP2 output
@@ -880,7 +949,8 @@ function gif (options) {
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function jp2 (options) { function jp2 (options) {
if (!this.constructor.format.jp2k.output.buffer) { /* node:coverage ignore next 41 */
if (!this.constructor.format.jp2.output.buffer) {
throw errJp2Save(); throw errJp2Save();
} }
if (is.object(options)) { if (is.object(options)) {
@@ -959,7 +1029,7 @@ function trySetAnimationOptions (source, target) {
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
* *
* The `density` can be set in pixels/inch via {@link #withmetadata|withMetadata} * The `density` can be set in pixels/inch via {@link #withmetadata withMetadata}
* instead of providing `xres` and `yres` in pixels/mm. * instead of providing `xres` and `yres` in pixels/mm.
* *
* @example * @example
@@ -976,6 +1046,7 @@ function trySetAnimationOptions (source, target) {
* @param {number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k * @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k
* @param {boolean} [options.bigtiff=false] - use BigTIFF variant (has no effect when compression is none)
* @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.pyramid=false] - write an image pyramid
* @param {boolean} [options.tile=false] - write a tiled tiff * @param {boolean} [options.tile=false] - write a tiled tiff
@@ -1054,6 +1125,10 @@ function tiff (options) {
throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression); throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression);
} }
} }
// bigtiff
if (is.defined(options.bigtiff)) {
this._setBooleanOption('tiffBigtiff', options.bigtiff);
}
// predictor // predictor
if (is.defined(options.predictor)) { if (is.defined(options.predictor)) {
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) { if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
@@ -1080,8 +1155,7 @@ function tiff (options) {
* AVIF image sequences are not supported. * AVIF image sequences are not supported.
* Prebuilt binaries support a bitdepth of 8 only. * Prebuilt binaries support a bitdepth of 8 only.
* *
* This feature is experimental on the Windows ARM64 platform * When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
* and requires a CPU with ARM64v8.4 or later.
* *
* @example * @example
* const data = await sharp(input) * const data = await sharp(input)
@@ -1101,11 +1175,13 @@ function tiff (options) {
* @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 * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr'
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function avif (options) { function avif (options) {
return this.heif({ ...options, compression: 'av1' }); const tune = is.object(options) && is.defined(options.tune) ? options.tune : 'iq';
return this.heif({ ...options, compression: 'av1', tune });
} }
/** /**
@@ -1128,6 +1204,7 @@ function avif (options) {
* @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 * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @param {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -1176,6 +1253,13 @@ function heif (options) {
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth); throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
} }
} }
if (is.defined(options.tune)) {
if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
this.options.heifTune = options.tune;
} else {
throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
}
}
} else { } else {
throw is.invalidParameterError('options', 'Object', options); throw is.invalidParameterError('options', 'Object', options);
} }
@@ -1189,7 +1273,7 @@ function heif (options) {
* *
* Requires libvips compiled with support for libjxl. * Requires libvips compiled with support for libjxl.
* The prebuilt binaries do not include this - see * The prebuilt binaries do not include this - see
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}. * {@link /install/#custom-libvips installing a custom libvips}.
* *
* @since 0.31.3 * @since 0.31.3
* *
@@ -1502,7 +1586,6 @@ function _setBooleanOption (key, val) {
* @private * @private
*/ */
function _read () { function _read () {
/* istanbul ignore else */
if (!this.options.streamOut) { if (!this.options.streamOut) {
this.options.streamOut = true; this.options.streamOut = true;
const stack = Error(); const stack = Error();
@@ -1619,16 +1702,18 @@ function _pipeline (callback, stack) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Public // Public
toFile, toFile,
toBuffer, toBuffer,
toUint8Array,
keepExif, keepExif,
withExif, withExif,
withExifMerge, withExifMerge,
keepIccProfile, keepIccProfile,
withIccProfile, withIccProfile,
withGainMap,
keepXmp, keepXmp,
withXmp, withXmp,
keepMetadata, keepMetadata,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const is = require('./is'); const is = require('./is');
@@ -540,10 +540,19 @@ function extract (options) {
* }) * })
* .toBuffer(); * .toBuffer();
* *
* @example
* // Trim image leaving (up to) a 10 pixel margin around the trimmed content.
* const output = await sharp(input)
* .trim({
* margin: 10
* })
* .toBuffer();
*
* @param {Object} [options] * @param {Object} [options]
* @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. * @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
* @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number. * @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
* @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic? * @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
* @param {number} [options.margin=0] - Leave a margin around trimmed content, value is in pixels.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -564,6 +573,13 @@ function trim (options) {
if (is.defined(options.lineArt)) { if (is.defined(options.lineArt)) {
this._setBooleanOption('trimLineArt', options.lineArt); this._setBooleanOption('trimLineArt', options.lineArt);
} }
if (is.defined(options.margin)) {
if (is.integer(options.margin) && options.margin >= 0) {
this.options.trimMargin = options.margin;
} else {
throw is.invalidParameterError('margin', 'positive integer', options.margin);
}
}
} else { } else {
throw is.invalidParameterError('trim', 'object', options); throw is.invalidParameterError('trim', 'object', options);
} }
@@ -579,7 +595,7 @@ function trim (options) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
resize, resize,
extend, extend,

View File

@@ -1,22 +1,25 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
// Inspects the runtime environment and exports the relevant sharp.node binary // Inspects the runtime environment and exports the relevant sharp.node binary
const { familySync, versionSync } = require('detect-libc'); const { familySync, versionSync } = require('detect-libc');
const { version } = require('../package.json');
const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips'); const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
const runtimePlatform = runtimePlatformArch(); const runtimePlatform = runtimePlatformArch();
const paths = [ const paths = [
`../src/build/Release/sharp-${runtimePlatform}.node`, `../src/build/Release/sharp-${runtimePlatform}-${version}.node`,
'../src/build/Release/sharp-wasm32.node', `../src/build/Release/sharp-wasm32-${version}.node`,
`@img/sharp-${runtimePlatform}/sharp.node`, `@img/sharp-${runtimePlatform}/sharp.node`,
'@img/sharp-wasm32/sharp.node' '@img/sharp-wasm32/sharp.node'
]; ];
/* node:coverage disable */
let path, sharp; let path, sharp;
const errors = []; const errors = [];
for (path of paths) { for (path of paths) {
@@ -24,12 +27,10 @@ for (path of paths) {
sharp = require(path); sharp = require(path);
break; break;
} catch (err) { } catch (err) {
/* istanbul ignore next */
errors.push(err); errors.push(err);
} }
} }
/* istanbul ignore next */
if (sharp && path.startsWith('@img/sharp-linux-x64') && !sharp._isUsingX64V2()) { if (sharp && path.startsWith('@img/sharp-linux-x64') && !sharp._isUsingX64V2()) {
const err = new Error('Prebuilt binaries for linux-x64 require v2 microarchitecture'); const err = new Error('Prebuilt binaries for linux-x64 require v2 microarchitecture');
err.code = 'Unsupported CPU'; err.code = 'Unsupported CPU';
@@ -37,7 +38,6 @@ if (sharp && path.startsWith('@img/sharp-linux-x64') && !sharp._isUsingX64V2())
sharp = null; sharp = null;
} }
/* istanbul ignore next */
if (sharp) { if (sharp) {
module.exports = sharp; module.exports = sharp;
} else { } else {
@@ -73,6 +73,7 @@ if (sharp) {
} else { } else {
help.push( help.push(
`- Manually install libvips >= ${minimumLibvipsVersion}`, `- Manually install libvips >= ${minimumLibvipsVersion}`,
' See https://sharp.pixelplumbing.com/install#building-from-source',
'- Add experimental WebAssembly-based dependencies:', '- Add experimental WebAssembly-based dependencies:',
' npm install --cpu=wasm32 sharp', ' npm install --cpu=wasm32 sharp',
' npm install @img/sharp-wasm32' ' npm install @img/sharp-wasm32'
@@ -88,7 +89,7 @@ if (sharp) {
` Found ${libcFound}`, ` Found ${libcFound}`,
` Requires ${libcRequires}` ` Requires ${libcRequires}`
); );
} catch (errEngines) {} } catch (_errEngines) {}
} }
if (isLinux && /\/snap\/core[0-9]{2}/.test(messages)) { if (isLinux && /\/snap\/core[0-9]{2}/.test(messages)) {
help.push( help.push(

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const events = require('node:events'); const events = require('node:events');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
@@ -24,7 +24,7 @@ const format = sharp.format();
format.heif.output.alias = ['avif', 'heic']; format.heif.output.alias = ['avif', 'heic'];
format.jpeg.output.alias = ['jpe', 'jpg']; format.jpeg.output.alias = ['jpe', 'jpg'];
format.tiff.output.alias = ['tif']; format.tiff.output.alias = ['tif'];
format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx']; format.jp2.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
/** /**
* An Object containing the available interpolators and their proper values * An Object containing the available interpolators and their proper values
@@ -57,7 +57,7 @@ const interpolators = {
let versions = { let versions = {
vips: libvipsVersion.semver vips: libvipsVersion.semver
}; };
/* istanbul ignore next */ /* node:coverage ignore next 15 */
if (!libvipsVersion.isGlobal) { if (!libvipsVersion.isGlobal) {
if (!libvipsVersion.isWasm) { if (!libvipsVersion.isWasm) {
try { try {
@@ -75,7 +75,7 @@ if (!libvipsVersion.isGlobal) {
} }
versions.sharp = require('../package.json').version; versions.sharp = require('../package.json').version;
/* istanbul ignore next */ /* node:coverage ignore next 5 */
if (versions.heif && format.heif) { if (versions.heif && format.heif) {
// Prebuilt binaries provide AV1 // Prebuilt binaries provide AV1
format.heif.input.fileSuffix = ['.avif']; format.heif.input.fileSuffix = ['.avif'];
@@ -136,7 +136,7 @@ cache(true);
* and these are independent of the value set here. * and these are independent of the value set here.
* *
* :::note * :::note
* Further {@link /performance|control over performance} is available. * Further {@link /performance/ control over performance} is available.
* ::: * :::
* *
* @example * @example
@@ -150,7 +150,7 @@ cache(true);
function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
} }
/* istanbul ignore next */ /* node:coverage ignore next 7 */
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);
@@ -277,7 +277,7 @@ function unblock (options) {
* @module Sharp * @module Sharp
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = (Sharp) => {
Sharp.cache = cache; Sharp.cache = cache;
Sharp.concurrency = concurrency; Sharp.concurrency = concurrency;
Sharp.counters = counters; Sharp.counters = counters;

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-arm64", "name": "@img/sharp-darwin-arm64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.3" "@img/sharp-libvips-darwin-arm64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-darwin-arm64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"os": [ "os": [
"darwin" "darwin"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-x64", "name": "@img/sharp-darwin-x64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.3" "@img/sharp-libvips-darwin-x64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-darwin-x64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"os": [ "os": [
"darwin" "darwin"

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
// Populate the npm package for the current platform with the local build // Populate the npm package for the current platform with the local build
@@ -44,9 +44,10 @@ cpSync(releaseDir, libDir, {
} }
}); });
// Generate README // Generate README and index.cjs
const { name, description } = require(`./${platform}/package.json`); const { version, name, description } = require(`./${platform}/package.json`);
writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`); writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
writeFileSync(join(destDir, 'index.cjs'), `module.exports = require('./lib/sharp-${platform}-${version}.node');`);
// Copy Apache-2.0 LICENSE // Copy Apache-2.0 LICENSE
copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE')); copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE'));

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm", "name": "@img/sharp-linux-arm",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.3" "@img/sharp-libvips-linux-arm": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linux-arm.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"glibc": ">=2.31" "glibc": ">=2.31"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm64", "name": "@img/sharp-linux-arm64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.3" "@img/sharp-libvips-linux-arm64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linux-arm64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"glibc": ">=2.26" "glibc": ">=2.26"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-ppc64", "name": "@img/sharp-linux-ppc64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"description": "Prebuilt sharp for use with Linux (glibc) ppc64", "description": "Prebuilt sharp for use with Linux (glibc) ppc64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.3" "@img/sharp-libvips-linux-ppc64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linux-ppc64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"glibc": ">=2.36" "glibc": ">=2.36"

View File

@@ -0,0 +1,47 @@
{
"name": "@img/sharp-linux-riscv64",
"version": "0.35.0-rc.0",
"description": "Prebuilt sharp for use with Linux (glibc) RISC-V 64-bit",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"repository": {
"type": "git",
"url": "git+https://github.com/lovell/sharp.git",
"directory": "npm/linux-riscv64"
},
"license": "Apache-2.0",
"funding": {
"url": "https://opencollective.com/libvips"
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.0-rc.2"
},
"files": [
"index.cjs",
"lib"
],
"publishConfig": {
"access": "public"
},
"type": "commonjs",
"exports": {
"./sharp.node": "./index.cjs",
"./package": "./package.json"
},
"engines": {
"node": ">=20.9.0"
},
"config": {
"glibc": ">=2.41"
},
"os": [
"linux"
],
"libc": [
"glibc"
],
"cpu": [
"riscv64"
]
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-s390x", "name": "@img/sharp-linux-s390x",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.3" "@img/sharp-libvips-linux-s390x": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linux-s390x.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"glibc": ">=2.36" "glibc": ">=2.36"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-x64", "name": "@img/sharp-linux-x64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.3" "@img/sharp-libvips-linux-x64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linux-x64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"glibc": ">=2.26" "glibc": ">=2.26"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-arm64", "name": "@img/sharp-linuxmusl-arm64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3" "@img/sharp-libvips-linuxmusl-arm64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linuxmusl-arm64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"musl": ">=1.2.2" "musl": ">=1.2.2"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-x64", "name": "@img/sharp-linuxmusl-x64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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,9 +15,10 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.3" "@img/sharp-libvips-linuxmusl-x64": "1.3.0-rc.2"
}, },
"files": [ "files": [
"index.cjs",
"lib" "lib"
], ],
"publishConfig": { "publishConfig": {
@@ -25,11 +26,11 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-linuxmusl-x64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"musl": ">=1.2.2" "musl": ">=1.2.2"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp", "name": "@img/sharp",
"version": "0.34.4", "version": "0.35.0-rc.0",
"private": "true", "private": "true",
"workspaces": [ "workspaces": [
"darwin-arm64", "darwin-arm64",
@@ -8,6 +8,7 @@
"linux-arm", "linux-arm",
"linux-arm64", "linux-arm64",
"linux-ppc64", "linux-ppc64",
"linux-riscv64",
"linux-s390x", "linux-s390x",
"linux-x64", "linux-x64",
"linuxmusl-arm64", "linuxmusl-arm64",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-wasm32", "name": "@img/sharp-wasm32",
"version": "0.34.4", "version": "0.35.0-rc.0",
"description": "Prebuilt sharp for use with wasm32", "description": "Prebuilt sharp for use with wasm32",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -23,15 +23,15 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-wasm32.node.js", "./sharp.node": "./index.cjs",
"./package": "./package.json", "./package": "./package.json",
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.5.0" "@emnapi/runtime": "^1.7.1"
}, },
"cpu": [ "cpu": [
"wasm32" "wasm32"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-arm64", "name": "@img/sharp-win32-arm64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"description": "Prebuilt sharp for use with Windows 64-bit ARM", "description": "Prebuilt sharp for use with Windows 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,6 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"files": [ "files": [
"index.cjs",
"lib", "lib",
"versions.json" "versions.json"
], ],
@@ -23,12 +24,12 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-win32-arm64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json", "./package": "./package.json",
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"os": [ "os": [
"win32" "win32"

View File

@@ -1,7 +1,7 @@
{ {
"name": "@img/sharp-win32-ia32", "name": "@img/sharp-win32-ia32",
"version": "0.34.4", "version": "0.35.0-rc.0",
"description": "Prebuilt sharp for use with Windows x86 (32-bit)", "description": "Prebuilt sharp for use with Windows x86 (deprecated)",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
"repository": { "repository": {
@@ -15,6 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"files": [ "files": [
"index.cjs",
"lib", "lib",
"versions.json" "versions.json"
], ],
@@ -23,12 +24,12 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-win32-ia32.node", "./sharp.node": "./index.cjs",
"./package": "./package.json", "./package": "./package.json",
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^20.9.0"
}, },
"os": [ "os": [
"win32" "win32"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-x64", "name": "@img/sharp-win32-x64",
"version": "0.34.4", "version": "0.35.0-rc.0",
"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",
@@ -15,6 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"files": [ "files": [
"index.cjs",
"lib", "lib",
"versions.json" "versions.json"
], ],
@@ -23,12 +24,12 @@
}, },
"type": "commonjs", "type": "commonjs",
"exports": { "exports": {
"./sharp.node": "./lib/sharp-win32-x64.node", "./sharp.node": "./index.cjs",
"./package": "./package.json", "./package": "./package.json",
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"os": [ "os": [
"win32" "win32"

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.34.4", "version": "0.35.0-rc.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
"contributors": [ "contributors": [
@@ -89,17 +89,19 @@
"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>", "Ingvar Stepanyan <me@rreverser.com>",
"Don Denton <don@happycollision.com>" "Don Denton <don@happycollision.com>",
"Dmytro Tiapukhin <cool.gegeg@gmail.com>"
], ],
"scripts": { "scripts": {
"install": "node install/check.js", "build": "node install/build.js",
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf src/build/ test/fixtures/output.*",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types", "test": "npm run lint && npm run test-unit",
"test-lint": "semistandard && cpplint", "lint": "npm run lint-cpp && npm run lint-js && npm run lint-types",
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha", "lint-cpp": "cpplint --quiet src/*.h src/*.cc",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"", "lint-js": "biome lint",
"lint-types": "tsd --files ./test/types/sharp.test-d.ts",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-types": "tsd", "test-unit": "node --experimental-test-coverage test/unit.mjs",
"package-from-local-build": "node npm/from-local-build.js", "package-from-local-build": "node npm/from-local-build.js",
"package-release-notes": "node npm/release-notes.js", "package-release-notes": "node npm/release-notes.js",
"docs-build": "node docs/build.mjs", "docs-build": "node docs/build.mjs",
@@ -138,83 +140,62 @@
], ],
"dependencies": { "dependencies": {
"@img/colour": "^1.0.0", "@img/colour": "^1.0.0",
"detect-libc": "^2.1.0", "detect-libc": "^2.1.2",
"semver": "^7.7.2" "semver": "^7.7.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-arm64": "0.35.0-rc.0",
"@img/sharp-darwin-x64": "0.34.4", "@img/sharp-darwin-x64": "0.35.0-rc.0",
"@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-riscv64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linux-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.0-rc.2",
"@img/sharp-linux-arm": "0.34.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.0-rc.2",
"@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-arm": "0.35.0-rc.0",
"@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-arm64": "0.35.0-rc.0",
"@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-ppc64": "0.35.0-rc.0",
"@img/sharp-linux-x64": "0.34.4", "@img/sharp-linux-riscv64": "0.35.0-rc.0",
"@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linux-s390x": "0.35.0-rc.0",
"@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-linux-x64": "0.35.0-rc.0",
"@img/sharp-wasm32": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.35.0-rc.0",
"@img/sharp-win32-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.35.0-rc.0",
"@img/sharp-win32-ia32": "0.34.4", "@img/sharp-wasm32": "0.35.0-rc.0",
"@img/sharp-win32-x64": "0.34.4" "@img/sharp-win32-arm64": "0.35.0-rc.0",
"@img/sharp-win32-ia32": "0.35.0-rc.0",
"@img/sharp-win32-x64": "0.35.0-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@emnapi/runtime": "^1.5.0", "@biomejs/biome": "^2.3.10",
"@img/sharp-libvips-dev": "1.2.3", "@cpplint/cli": "^0.1.0",
"@img/sharp-libvips-dev-wasm32": "1.2.3", "@emnapi/runtime": "^1.7.1",
"@img/sharp-libvips-win32-arm64": "1.2.3", "@img/sharp-libvips-dev": "1.3.0-rc.2",
"@img/sharp-libvips-win32-ia32": "1.2.3", "@img/sharp-libvips-dev-wasm32": "1.3.0-rc.2",
"@img/sharp-libvips-win32-x64": "1.2.3", "@img/sharp-libvips-win32-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-win32-ia32": "1.3.0-rc.2",
"@img/sharp-libvips-win32-x64": "1.3.0-rc.2",
"@types/node": "*", "@types/node": "*",
"cc": "^3.0.1", "emnapi": "^1.7.1",
"emnapi": "^1.5.0", "exif-reader": "^2.0.3",
"exif-reader": "^2.0.2",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"icc": "^3.0.0", "icc": "^3.0.0",
"jsdoc-to-markdown": "^9.1.2",
"license-checker": "^25.0.1",
"mocha": "^11.7.2",
"node-addon-api": "^8.5.0", "node-addon-api": "^8.5.0",
"node-gyp": "^11.4.2", "node-gyp": "^12.1.0",
"nyc": "^17.1.0",
"semistandard": "^17.0.0",
"tar-fs": "^3.1.1", "tar-fs": "^3.1.1",
"tsd": "^0.33.0" "tsd": "^0.33.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"libvips": ">=8.17.2" "libvips": ">=8.18.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
},
"semistandard": {
"env": [
"mocha"
]
},
"cc": {
"linelength": "120",
"filter": [
"build/include"
]
},
"nyc": {
"include": [
"lib"
]
},
"tsd": {
"directory": "test/types/"
} }
} }

10
src/CPPLINT.cfg Normal file
View File

@@ -0,0 +1,10 @@
set noparent
linelength=120
filter=-build/include
filter=+build/include_alpha
filter=+build/include_subdir
filter=+build/include_what_you_use
filter=-whitespace/indent_namespace

View File

@@ -5,6 +5,7 @@
'variables': { 'variables': {
'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_version': '<!(node -p "require(\'../package.json\').version")',
'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_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()")',
@@ -81,7 +82,7 @@
}] }]
] ]
}, { }, {
'target_name': 'sharp-<(platform_and_arch)', 'target_name': 'sharp-<(platform_and_arch)-<(sharp_version)',
'defines': [ 'defines': [
'G_DISABLE_ASSERT', 'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS', 'G_DISABLE_CAST_CHECKS',
@@ -120,7 +121,7 @@
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
# Use pkg-config for include and lib # Use pkg-config for include and lib
'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'], 'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s/-I//g)'],
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'], 'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'],
'defines': [ 'defines': [
'SHARP_USE_GLOBAL_LIBVIPS' 'SHARP_USE_GLOBAL_LIBVIPS'
@@ -282,7 +283,7 @@
'target_name': 'copy-dll', 'target_name': 'copy-dll',
'type': 'none', 'type': 'none',
'dependencies': [ 'dependencies': [
'sharp-<(platform_and_arch)' 'sharp-<(platform_and_arch)-<(sharp_version)'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {

View File

@@ -1,18 +1,22 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <string>
#include <string.h>
#include <vector>
#include <queue>
#include <map> #include <map>
#include <mutex> // NOLINT(build/c++11) #include <mutex>
#include <queue>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "./common.h"
using vips::VImage; using vips::VImage;
@@ -285,6 +289,7 @@ namespace sharp {
case ImageType::JXL: id = "jxl"; break; case ImageType::JXL: id = "jxl"; break;
case ImageType::RAD: id = "rad"; break; case ImageType::RAD: id = "rad"; break;
case ImageType::DCRAW: id = "dcraw"; break; case ImageType::DCRAW: id = "dcraw"; break;
case ImageType::UHDR: id = "uhdr"; break;
case ImageType::VIPS: id = "vips"; break; case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
@@ -335,6 +340,9 @@ namespace sharp {
{ "VipsForeignLoadRadBuffer", ImageType::RAD }, { "VipsForeignLoadRadBuffer", ImageType::RAD },
{ "VipsForeignLoadDcRawFile", ImageType::DCRAW }, { "VipsForeignLoadDcRawFile", ImageType::DCRAW },
{ "VipsForeignLoadDcRawBuffer", ImageType::DCRAW }, { "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
{ "VipsForeignLoadUhdr", ImageType::UHDR },
{ "VipsForeignLoadUhdrFile", ImageType::UHDR },
{ "VipsForeignLoadUhdrBuffer", ImageType::UHDR },
{ "VipsForeignLoadVips", ImageType::VIPS }, { "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS }, { "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW } { "VipsForeignLoadRaw", ImageType::RAW }
@@ -352,6 +360,9 @@ namespace sharp {
imageType = it->second; imageType = it->second;
} }
} }
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType; return imageType;
} }
@@ -371,6 +382,9 @@ namespace sharp {
imageType = ImageType::MISSING; imageType = ImageType::MISSING;
} }
} }
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType; return imageType;
} }
@@ -1123,4 +1137,20 @@ namespace sharp {
} }
return image; return image;
} }
/*
Does this image have a gain map?
*/
bool HasGainMap(VImage image) {
return image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB;
}
/*
Removes gain map, if any.
*/
VImage RemoveGainMap(VImage image) {
VImage copy = image.copy();
copy.remove("gainmap-data");
return copy;
}
} // namespace sharp } // namespace sharp

View File

@@ -1,13 +1,16 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_COMMON_H_ #ifndef SRC_COMMON_H_
#define SRC_COMMON_H_ #define SRC_COMMON_H_
#include <atomic>
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <utility>
#include <vector> #include <vector>
#include <atomic>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@@ -15,9 +18,9 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8) || \ #if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 18) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 2) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION < 0)
#error "libvips version 8.17.2+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.18.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if defined(__has_include) #if defined(__has_include)
@@ -30,7 +33,7 @@ using vips::VImage;
namespace sharp { namespace sharp {
struct InputDescriptor { // NOLINT(runtime/indentation_namespace) struct InputDescriptor {
std::string name; std::string name;
std::string file; std::string file;
bool autoOrient; bool autoOrient;
@@ -170,6 +173,7 @@ namespace sharp {
JXL, JXL,
RAD, RAD,
DCRAW, DCRAW,
UHDR,
VIPS, VIPS,
RAW, RAW,
UNKNOWN, UNKNOWN,
@@ -394,6 +398,16 @@ namespace sharp {
*/ */
VImage StaySequential(VImage image, bool condition = true); VImage StaySequential(VImage image, bool condition = true);
/*
Does this image have a gain map?
*/
bool HasGainMap(VImage image);
/*
Removes gain map, if any.
*/
VImage RemoveGainMap(VImage image);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

View File

@@ -1,5 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
/* global Module, ENV, _vips_shutdown, _uv_library_shutdown */ /* global Module, ENV, _vips_shutdown, _uv_library_shutdown */

View File

@@ -1,15 +1,19 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <numeric>
#include <vector>
#include <cmath> #include <cmath>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "./common.h"
#include "metadata.h" #include "./metadata.h"
static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p); static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p);
@@ -137,6 +141,14 @@ class MetadataWorker : public Napi::AsyncWorker {
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength); memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength; baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
} }
// Gain Map
if (image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB) {
size_t gainMapLength;
void const *gainMap = image.get_blob("gainmap-data", &gainMapLength);
baton->gainMap = static_cast<char *>(g_malloc(gainMapLength));
memcpy(baton->gainMap, gainMap, gainMapLength);
baton->gainMapLength = gainMapLength;
}
// PNG comments // PNG comments
vips_image_map(image.get_image(), readPNGComment, &baton->comments); vips_image_map(image.get_image(), readPNGComment, &baton->comments);
} }
@@ -178,10 +190,6 @@ class MetadataWorker : public Napi::AsyncWorker {
info.Set("isPalette", baton->isPalette); info.Set("isPalette", baton->isPalette);
if (baton->bitsPerSample > 0) { if (baton->bitsPerSample > 0) {
info.Set("bitsPerSample", baton->bitsPerSample); info.Set("bitsPerSample", baton->bitsPerSample);
if (baton->isPalette) {
// Deprecated, remove with libvips 8.17.0
info.Set("paletteBitDepth", baton->bitsPerSample);
}
} }
if (baton->pages > 0) { if (baton->pages > 0) {
info.Set("pages", baton->pages); info.Set("pages", baton->pages);
@@ -215,10 +223,10 @@ class MetadataWorker : public Napi::AsyncWorker {
if (!baton->levels.empty()) { if (!baton->levels.empty()) {
int i = 0; int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size())); Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
for (std::pair<int, int> const &l : baton->levels) { for (const auto& [width, height] : baton->levels) {
Napi::Object level = Napi::Object::New(env); Napi::Object level = Napi::Object::New(env);
level.Set("width", l.first); level.Set("width", width);
level.Set("height", l.second); level.Set("height", height);
levels.Set(i++, level); levels.Set(i++, level);
} }
info.Set("levels", levels); info.Set("levels", levels);
@@ -272,13 +280,19 @@ 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->gainMapLength > 0) {
Napi::Object gainMap = Napi::Object::New(env);
info.Set("gainMap", gainMap);
gainMap.Set("image",
Napi::Buffer<char>::NewOrCopy(env, baton->gainMap, baton->gainMapLength, sharp::FreeCallback));
}
if (baton->comments.size() > 0) { if (baton->comments.size() > 0) {
int i = 0; int i = 0;
Napi::Array comments = Napi::Array::New(env, baton->comments.size()); Napi::Array comments = Napi::Array::New(env, baton->comments.size());
for (auto &c : baton->comments) { for (const auto& [keyword, text] : baton->comments) {
Napi::Object comment = Napi::Object::New(env); Napi::Object comment = Napi::Object::New(env);
comment.Set("keyword", c.first); comment.Set("keyword", keyword);
comment.Set("text", c.second); comment.Set("text", text);
comments.Set(i++, comment); comments.Set(i++, comment);
} }
info.Set("comments", comments); info.Set("comments", comments);

View File

@@ -1,10 +1,13 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_METADATA_H_ #ifndef SRC_METADATA_H_
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include <string> #include <string>
#include <vector>
#include <napi.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -50,6 +53,8 @@ struct MetadataBaton {
size_t xmpLength; size_t xmpLength;
char *tifftagPhotoshop; char *tifftagPhotoshop;
size_t tifftagPhotoshopLength; size_t tifftagPhotoshopLength;
char *gainMap;
size_t gainMapLength;
MetadataComments comments; MetadataComments comments;
std::string err; std::string err;
@@ -79,7 +84,9 @@ struct MetadataBaton {
xmp(nullptr), xmp(nullptr),
xmpLength(0), xmpLength(0),
tifftagPhotoshop(nullptr), tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0),
gainMap(nullptr),
gainMapLength(0) {}
}; };
Napi::Value metadata(const Napi::CallbackInfo& info); Napi::Value metadata(const Napi::CallbackInfo& info);

View File

@@ -1,5 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
@@ -8,8 +10,8 @@
#include <vector> #include <vector>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "./common.h"
#include "operations.h" #include "./operations.h"
using vips::VImage; using vips::VImage;
using vips::VError; using vips::VError;
@@ -283,7 +285,7 @@ namespace sharp {
/* /*
Trim an image Trim an image
*/ */
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) { VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin) {
if (image.width() < 3 && image.height() < 3) { if (image.width() < 3 && image.height() < 3) {
throw VError("Image to trim must be at least 3x3 pixels"); throw VError("Image to trim must be at least 3x3 pixels");
} }
@@ -318,18 +320,36 @@ namespace sharp {
if (widthA > 0 && heightA > 0) { if (widthA > 0 && heightA > 0) {
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
// Combined bounding box (B) // Combined bounding box (B)
int const leftB = std::min(left, leftA); int leftB = std::min(left, leftA);
int const topB = std::min(top, topA); int topB = std::min(top, topA);
int const widthB = std::max(left + width, leftA + widthA) - leftB; int widthB = std::max(left + width, leftA + widthA) - leftB;
int const heightB = std::max(top + height, topA + heightA) - topB; int heightB = std::max(top + height, topA + heightA) - topB;
if (margin > 0) {
leftB = std::max(0, leftB - margin);
topB = std::max(0, topB - margin);
widthB = std::min(image.width() - leftB, widthB + 2 * margin);
heightB = std::min(image.height() - topB, heightB + 2 * margin);
}
return image.extract_area(leftB, topB, widthB, heightB); return image.extract_area(leftB, topB, widthB, heightB);
} else { } else {
// Use alpha only // Use alpha only
if (margin > 0) {
leftA = std::max(0, leftA - margin);
topA = std::max(0, topA - margin);
widthA = std::min(image.width() - leftA, widthA + 2 * margin);
heightA = std::min(image.height() - topA, heightA + 2 * margin);
}
return image.extract_area(leftA, topA, widthA, heightA); return image.extract_area(leftA, topA, widthA, heightA);
} }
} }
} }
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
if (margin > 0) {
left = std::max(0, left - margin);
top = std::max(0, top - margin);
width = std::min(image.width() - left, width + 2 * margin);
height = std::min(image.height() - top, height + 2 * margin);
}
return image.extract_area(left, top, width, height); return image.extract_area(left, top, width, height);
} }
return image; return image;

View File

@@ -1,5 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_OPERATIONS_H_ #ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_ #define SRC_OPERATIONS_H_
@@ -8,6 +10,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <vector>
#include <vips/vips8> #include <vips/vips8>
using vips::VImage; using vips::VImage;
@@ -79,7 +82,7 @@ namespace sharp {
/* /*
Trim an image Trim an image
*/ */
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt); VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin);
/* /*
* Linear adjustment (a * in + b) * Linear adjustment (a * in + b)

View File

@@ -1,9 +1,11 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <filesystem> #include <filesystem> // NOLINT(build/c++17)
#include <map> #include <map>
#include <memory> #include <memory>
#include <numeric> #include <numeric>
@@ -17,9 +19,9 @@
#include <vips/vips8> #include <vips/vips8>
#include <napi.h> #include <napi.h>
#include "common.h" #include "./common.h"
#include "operations.h" #include "./operations.h"
#include "pipeline.h" #include "./pipeline.h"
class PipelineWorker : public Napi::AsyncWorker { class PipelineWorker : public Napi::AsyncWorker {
public: public:
@@ -151,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->trimThreshold >= 0.0) { if (baton->trimThreshold >= 0.0) {
MultiPageUnsupported(nPages, "Trim"); MultiPageUnsupported(nPages, "Trim");
image = sharp::StaySequential(image); 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->trimMargin);
baton->trimOffsetLeft = image.xoffset(); baton->trimOffsetLeft = image.xoffset();
baton->trimOffsetTop = image.yoffset(); baton->trimOffsetTop = image.yoffset();
} }
@@ -294,6 +296,14 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->input->autoOrient) { if (baton->input->autoOrient) {
image = sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
if (sharp::HasGainMap(image)) {
if (baton->withGainMap) {
image = image.uhdr2scRGB();
}
image = sharp::RemoveGainMap(image);
} else {
baton->withGainMap = false;
}
// Any pre-shrinking may already have been done // Any pre-shrinking may already have been done
inputWidth = image.width(); inputWidth = image.width();
@@ -333,7 +343,7 @@ class PipelineWorker : public Napi::AsyncWorker {
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->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
!baton->input->ignoreIcc !baton->input->ignoreIcc && !baton->withGainMap
) { ) {
// Convert to sRGB/P3 using embedded profile // Convert to sRGB/P3 using embedded profile
try { try {
@@ -453,12 +463,10 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha); std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
// Embed // Embed
int left; const auto& [left, top] = sharp::CalculateEmbedPosition(
int top;
std::tie(left, top) = sharp::CalculateEmbedPosition(
inputWidth, inputHeight, baton->width, baton->height, baton->position); inputWidth, inputHeight, baton->width, baton->height, baton->position);
int width = std::max(inputWidth, baton->width); const int width = std::max(inputWidth, baton->width);
int height = std::max(inputHeight, baton->height); const int height = std::max(inputHeight, baton->height);
image = nPages > 1 image = nPages > 1
? sharp::EmbedMultiPage(image, ? sharp::EmbedMultiPage(image,
@@ -477,13 +485,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Crop // Crop
if (baton->position < 9) { if (baton->position < 9) {
// Gravity-based crop // Gravity-based crop
int left; const auto& [left, top] = sharp::CalculateCrop(
int top;
std::tie(left, top) = sharp::CalculateCrop(
inputWidth, inputHeight, baton->width, baton->height, baton->position); inputWidth, inputHeight, baton->width, baton->height, baton->position);
int width = std::min(inputWidth, baton->width); const int width = std::min(inputWidth, baton->width);
int height = std::min(inputHeight, baton->height); const int height = std::min(inputHeight, baton->height);
image = nPages > 1 image = nPages > 1
? sharp::CropMultiPage(image, ? sharp::CropMultiPage(image,
@@ -795,20 +800,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::EnsureAlpha(image, baton->ensureAlpha); image = sharp::EnsureAlpha(image, baton->ensureAlpha);
} }
// Convert image to sRGB, if not already // Ensure output colour space
if (sharp::Is16Bit(image.interpretation())) { if (sharp::Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT); image = image.cast(VIPS_FORMAT_USHORT);
} }
if (image.interpretation() != baton->colourspace) { if (image.interpretation() != baton->colourspace) {
// 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 if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && image = sharp::SetProfile(image, inputProfile);
baton->withIccProfile.empty() && sharp::HasProfile(image)) {
image = image.icc_transform(processingProfile, VImage::option()
->set("embedded", true)
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL));
} }
} }
@@ -843,8 +842,6 @@ class PipelineWorker : public Napi::AsyncWorker {
} catch(...) { } catch(...) {
sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr); 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 // Negate the colours in the image
@@ -866,8 +863,8 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!baton->withExifMerge) { if (!baton->withExifMerge) {
image = sharp::RemoveExif(image); image = sharp::RemoveExif(image);
} }
for (const auto& s : baton->withExif) { for (const auto& [key, value] : baton->withExif) {
image.set(s.first.data(), s.second.data()); image.set(key.c_str(), value.c_str());
} }
} }
// XMP buffer // XMP buffer
@@ -968,6 +965,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort) ->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize) ->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed) ->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality))); ->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@@ -1009,6 +1007,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("bitdepth", baton->tiffBitdepth) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("bigtiff", baton->tiffBigtiff)
->set("miniswhite", baton->tiffMiniswhite) ->set("miniswhite", baton->tiffMiniswhite)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -1034,6 +1033,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort) ->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth) ->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->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)));
@@ -1178,6 +1178,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort) ->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize) ->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed) ->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) || } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1211,6 +1212,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("bitdepth", baton->tiffBitdepth) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("bigtiff", baton->tiffBigtiff)
->set("miniswhite", baton->tiffMiniswhite) ->set("miniswhite", baton->tiffMiniswhite)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -1232,6 +1234,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort) ->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth) ->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->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));
@@ -1276,7 +1279,12 @@ class PipelineWorker : public Napi::AsyncWorker {
if (what && what[0]) { if (what && what[0]) {
(baton->err).append(what); (baton->err).append(what);
} else { } else {
(baton->err).append("Unknown error"); if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
(baton->err).append("Warning treated as error due to failOn setting");
baton->errUseWarning = true;
} else {
(baton->err).append("Unknown error");
}
} }
} }
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
@@ -1291,7 +1299,11 @@ class PipelineWorker : public Napi::AsyncWorker {
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); if (baton->errUseWarning) {
(baton->err).append("\n").append(warning);
} else {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
}
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
@@ -1337,12 +1349,21 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Add buffer size to info
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength)); info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
// Pass ownership of output data to Buffer instance if (baton->typedArrayOut) {
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut), // ECMAScript ArrayBuffer with Uint8Array view
baton->bufferOutLength, sharp::FreeCallback); Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
Callback().Call(Receiver().Value(), { env.Null(), data, info }); memcpy(ab.Data(), baton->bufferOut, baton->bufferOutLength);
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
Napi::TypedArrayOf<uint8_t> data = Napi::TypedArrayOf<uint8_t>::New(env,
baton->bufferOutLength, ab, 0, napi_uint8_array);
Callback().Call(Receiver().Value(), { env.Null(), data, info });
} else {
// Node.js Buffer
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().Call(Receiver().Value(), { env.Null(), data, info });
}
} else { } else {
// Add file size to info // Add file size to info
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) { if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
@@ -1435,11 +1456,11 @@ class PipelineWorker : public Napi::AsyncWorker {
std::string std::string
AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) { AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
std::string argument; std::string argument;
for (auto const &option : options) { for (const auto& [key, value] : options) {
if (!argument.empty()) { if (!argument.empty()) {
argument += ","; argument += ",";
} }
argument += option.first + "=" + option.second; argument += key + "=" + value;
} }
return extname + "[" + argument + "]"; return extname + "[" + argument + "]";
} }
@@ -1469,6 +1490,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"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"},
{"exact", baton->webpExact ? "true" : "false"},
{"effort", std::to_string(baton->webpEffort)} {"effort", std::to_string(baton->webpEffort)}
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
@@ -1615,6 +1637,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground"); baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt"); baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
baton->gamma = sharp::AttrAsDouble(options, "gamma"); baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA"); baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1692,6 +1715,7 @@ 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->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata"); 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");
@@ -1706,6 +1730,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
} }
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
baton->withXmp = sharp::AttrAsStr(options, "withXmp"); baton->withXmp = sharp::AttrAsStr(options, "withXmp");
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
baton->loop = sharp::AttrAsUint32(options, "loop"); baton->loop = sharp::AttrAsUint32(options, "loop");
baton->delay = sharp::AttrAsInt32Vector(options, "delay"); baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1741,6 +1766,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort"); baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize"); baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed"); baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth"); baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort"); baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
@@ -1750,6 +1776,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite"); baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
@@ -1774,6 +1801,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
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->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
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");

View File

@@ -1,13 +1,15 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_PIPELINE_H_ #ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_ #define SRC_PIPELINE_H_
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include <unordered_map> #include <unordered_map>
#include <vector>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@@ -46,6 +48,7 @@ struct PipelineBaton {
size_t bufferOutLength; size_t bufferOutLength;
int pageHeightOut; int pageHeightOut;
int pagesOut; int pagesOut;
bool typedArrayOut;
std::vector<Composite *> composite; std::vector<Composite *> composite;
std::vector<sharp::InputDescriptor *> joinChannelIn; std::vector<sharp::InputDescriptor *> joinChannelIn;
int topOffsetPre; int topOffsetPre;
@@ -99,6 +102,7 @@ struct PipelineBaton {
bool trimLineArt; bool trimLineArt;
int trimOffsetLeft; int trimOffsetLeft;
int trimOffsetTop; int trimOffsetTop;
int trimMargin;
std::vector<double> linearA; std::vector<double> linearA;
std::vector<double> linearB; std::vector<double> linearB;
int dilateWidth; int dilateWidth;
@@ -165,6 +169,7 @@ struct PipelineBaton {
int webpEffort; int webpEffort;
bool webpMinSize; bool webpMinSize;
bool webpMixed; bool webpMixed;
bool webpExact;
int gifBitdepth; int gifBitdepth;
int gifEffort; int gifEffort;
double gifDither; double gifDither;
@@ -175,6 +180,7 @@ struct PipelineBaton {
bool gifProgressive; bool gifProgressive;
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
bool tiffBigtiff;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid; bool tiffPyramid;
int tiffBitdepth; int tiffBitdepth;
@@ -191,12 +197,14 @@ struct PipelineBaton {
std::string heifChromaSubsampling; std::string heifChromaSubsampling;
bool heifLossless; bool heifLossless;
int heifBitdepth; int heifBitdepth;
std::string heifTune;
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 errUseWarning;
int keepMetadata; int keepMetadata;
int withMetadataOrientation; int withMetadataOrientation;
double withMetadataDensity; double withMetadataDensity;
@@ -204,6 +212,7 @@ struct PipelineBaton {
std::unordered_map<std::string, std::string> withExif; std::unordered_map<std::string, std::string> withExif;
bool withExifMerge; bool withExifMerge;
std::string withXmp; std::string withXmp;
bool withGainMap;
int timeoutSeconds; int timeoutSeconds;
std::vector<double> convKernel; std::vector<double> convKernel;
int convKernelWidth; int convKernelWidth;
@@ -238,6 +247,7 @@ struct PipelineBaton {
bufferOutLength(0), bufferOutLength(0),
pageHeightOut(0), pageHeightOut(0),
pagesOut(0), pagesOut(0),
typedArrayOut(false),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),
@@ -277,6 +287,7 @@ struct PipelineBaton {
trimLineArt(false), trimLineArt(false),
trimOffsetLeft(0), trimOffsetLeft(0),
trimOffsetTop(0), trimOffsetTop(0),
trimMargin(0),
linearA{}, linearA{},
linearB{}, linearB{},
dilateWidth(0), dilateWidth(0),
@@ -340,6 +351,7 @@ struct PipelineBaton {
webpEffort(4), webpEffort(4),
webpMinSize(false), webpMinSize(false),
webpMixed(false), webpMixed(false),
webpExact(false),
gifBitdepth(8), gifBitdepth(8),
gifEffort(7), gifEffort(7),
gifDither(1.0), gifDither(1.0),
@@ -350,6 +362,7 @@ struct PipelineBaton {
gifProgressive(false), gifProgressive(false),
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffBigtiff(false),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false), tiffPyramid(false),
tiffBitdepth(8), tiffBitdepth(8),
@@ -366,15 +379,18 @@ struct PipelineBaton {
heifChromaSubsampling("4:4:4"), heifChromaSubsampling("4:4:4"),
heifLossless(false), heifLossless(false),
heifBitdepth(8), heifBitdepth(8),
heifTune("ssim"),
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),
errUseWarning(false),
keepMetadata(0), keepMetadata(0),
withMetadataOrientation(-1), withMetadataOrientation(-1),
withMetadataDensity(0.0), withMetadataDensity(0.0),
withExifMerge(true), withExifMerge(true),
withGainMap(false),
timeoutSeconds(0), timeoutSeconds(0),
convKernelWidth(0), convKernelWidth(0),
convKernelHeight(0), convKernelHeight(0),

View File

@@ -1,16 +1,18 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <mutex> // NOLINT(build/c++11) #include <mutex>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "./common.h"
#include "metadata.h" #include "./metadata.h"
#include "pipeline.h" #include "./pipeline.h"
#include "utilities.h" #include "./stats.h"
#include "stats.h" #include "./utilities.h"
Napi::Object init(Napi::Env env, Napi::Object exports) { Napi::Object init(Napi::Env env, Napi::Object exports) {
static std::once_flag sharp_vips_init_once; static std::once_flag sharp_vips_init_once;

View File

@@ -1,15 +1,18 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <numeric>
#include <vector>
#include <iostream> #include <iostream>
#include <numeric>
#include <string>
#include <vector>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "./common.h"
#include "stats.h" #include "./stats.h"
class StatsWorker : public Napi::AsyncWorker { class StatsWorker : public Napi::AsyncWorker {
public: public:

View File

@@ -1,10 +1,13 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_STATS_H_ #ifndef SRC_STATS_H_
#define SRC_STATS_H_ #define SRC_STATS_H_
#include <string> #include <string>
#include <vector>
#include <napi.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -24,7 +27,7 @@ struct ChannelStats {
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal, ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal): double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), // NOLINT(build/include_what_you_use)
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
}; };

View File

@@ -1,17 +1,19 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#include <cmath> #include <cmath>
#include <string>
#include <cstdio> #include <cstdio>
#include <string>
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include <vips/vector.h> #include <vips/vector.h>
#include "common.h" #include "./common.h"
#include "operations.h" #include "./operations.h"
#include "utilities.h" #include "./utilities.h"
/* /*
Get and set cache limits Get and set cache limits
@@ -121,6 +123,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw"
}) { }) {
std::string id = f == "jp2k" ? "jp2" : f;
// Input // Input
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str()); const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc); Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
@@ -152,11 +155,11 @@ Napi::Value format(const Napi::CallbackInfo& info) {
output.Set("stream", hasOutputBuffer); output.Set("stream", hasOutputBuffer);
// Other attributes // Other attributes
Napi::Object container = Napi::Object::New(env); Napi::Object container = Napi::Object::New(env);
container.Set("id", f); container.Set("id", id);
container.Set("input", input); container.Set("input", input);
container.Set("output", output); container.Set("output", output);
// Add to set of formats // Add to set of formats
format.Set(f, container); format.Set(id, container);
} }
// Raw, uncompressed data // Raw, uncompressed data

View File

@@ -1,5 +1,7 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_

View File

@@ -1,24 +0,0 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
const sharp = require('../');
const usingCache = !process.env.G_DEBUG;
const usingSimd = !process.env.VIPS_NOVECTOR;
const concurrency = Number(process.env.VIPS_CONCURRENCY) || 0;
exports.mochaHooks = {
beforeEach () {
sharp.cache(usingCache);
sharp.simd(usingSimd);
sharp.concurrency(concurrency);
},
afterEach () {
if (global.gc) {
global.gc();
}
}
};

View File

@@ -15,7 +15,7 @@ RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
# Install sharp # Install sharp
RUN mkdir /tmp/sharp RUN mkdir /tmp/sharp
RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git
RUN cd /tmp/sharp && npm install --build-from-source RUN cd /tmp/sharp && npm install && npm run build
# Install benchmark test # Install benchmark test
RUN cd /tmp/sharp/test/bench && npm install --omit optional RUN cd /tmp/sharp/test/bench && npm install --omit optional

View File

@@ -1,11 +1,11 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
process.env.UV_THREADPOOL_SIZE = 64; process.env.UV_THREADPOOL_SIZE = 64;
const assert = require('assert'); const assert = require('node:assert');
const async = require('async'); const async = require('async');
const sharp = require('../../'); const sharp = require('../../');
@@ -16,32 +16,29 @@ const height = 480;
sharp.concurrency(1); sharp.concurrency(1);
const timer = setInterval(function () { const timer = setInterval(() => {
console.dir(sharp.counters()); console.dir(sharp.counters());
}, 100); }, 100);
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function (parallelism, next) { async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], (parallelism, next) => {
const start = new Date().getTime(); const start = Date.now();
async.times(parallelism, async.times(parallelism,
function (id, callback) { (_id, callback) => {
/* jslint unused: false */ sharp(fixtures.inputJpg).resize(width, height).toBuffer((err, buffer) => {
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function (err, buffer) {
buffer = null; buffer = null;
callback(err, new Date().getTime() - start); callback(err, Date.now() - start);
}); });
}, },
function (err, ids) { (err, ids) => {
assert(!err); assert(!err);
assert(ids.length === parallelism); assert(ids.length === parallelism);
ids.sort(); ids.sort();
const mean = ids.reduce(function (a, b) { const mean = ids.reduce((a, b) => a + b) / ids.length;
return a + b; console.log(`${parallelism} parallel calls: fastest=${ids[0]}ms slowest=${ids[ids.length - 1]}ms mean=${mean}ms`);
}) / ids.length;
console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms');
next(); next();
} }
); );
}, function () { }, () => {
clearInterval(timer); clearInterval(timer);
console.dir(sharp.counters()); console.dir(sharp.counters());
}); });

View File

@@ -1,10 +1,10 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const fs = require('node:fs');
const { execSync } = require('node:child_process');
const fs = require('fs');
const { execSync } = require('child_process');
const async = require('async'); const async = require('async');
const Benchmark = require('benchmark'); const Benchmark = require('benchmark');
@@ -12,7 +12,7 @@ const Benchmark = require('benchmark');
const safeRequire = (name) => { const safeRequire = (name) => {
try { try {
return require(name); return require(name);
} catch (err) {} } catch (_err) {}
return null; return null;
}; };
@@ -45,13 +45,13 @@ console.log(`Detected ${physicalCores} physical cores`);
sharp.concurrency(physicalCores); sharp.concurrency(physicalCores);
async.series({ async.series({
jpeg: function (callback) { jpeg: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
const jpegSuite = new Benchmark.Suite('jpeg'); const jpegSuite = new Benchmark.Suite('jpeg');
// jimp // jimp
jpegSuite.add('jimp-buffer-buffer', { jpegSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
fn: async function (deferred) { fn: async (deferred) => {
const image = await Jimp.read(inputJpgBuffer); const image = await Jimp.read(inputJpgBuffer);
await image await image
.resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC }) .resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC })
@@ -60,7 +60,7 @@ async.series({
} }
}).add('jimp-file-file', { }).add('jimp-file-file', {
defer: true, defer: true,
fn: async function (deferred) { fn: async (deferred) => {
const image = await Jimp.read(fixtures.inputJpg); const image = await Jimp.read(fixtures.inputJpg);
await image await image
.resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC }) .resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC })
@@ -71,14 +71,14 @@ async.series({
// mapnik // mapnik
mapnik && jpegSuite.add('mapnik-file-file', { mapnik && jpegSuite.add('mapnik-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
mapnik.Image.open(fixtures.inputJpg, function (err, img) { mapnik.Image.open(fixtures.inputJpg, (err, img) => {
if (err) throw err; if (err) throw err;
img img
.resize(width, height, { .resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos scaling_method: mapnik.imageScaling.lanczos
}) })
.save(outputJpg, 'jpeg:quality=80', function (err) { .save(outputJpg, 'jpeg:quality=80', (err) => {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -86,14 +86,14 @@ async.series({
} }
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) { mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, (err, img) => {
if (err) throw err; if (err) throw err;
img img
.resize(width, height, { .resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos scaling_method: mapnik.imageScaling.lanczos
}) })
.encode('jpeg:quality=80', function (err) { .encode('jpeg:quality=80', (err) => {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -103,7 +103,7 @@ async.series({
// imagemagick // imagemagick
jpegSuite.add('imagemagick-file-file', { jpegSuite.add('imagemagick-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputJpg, srcPath: fixtures.inputJpg,
dstPath: outputJpg, dstPath: outputJpg,
@@ -112,7 +112,7 @@ async.series({
height, height,
format: 'jpg', format: 'jpg',
filter: 'Lanczos' filter: 'Lanczos'
}, function (err) { }, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -124,12 +124,12 @@ async.series({
// gm // gm
jpegSuite.add('gm-buffer-file', { jpegSuite.add('gm-buffer-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(inputJpgBuffer) gm(inputJpgBuffer)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.write(outputJpg, function (err) { .write(outputJpg, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -139,12 +139,12 @@ async.series({
} }
}).add('gm-buffer-buffer', { }).add('gm-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(inputJpgBuffer) gm(inputJpgBuffer)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -154,12 +154,12 @@ async.series({
} }
}).add('gm-file-file', { }).add('gm-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.write(outputJpg, function (err) { .write(outputJpg, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -169,12 +169,12 @@ async.series({
} }
}).add('gm-file-buffer', { }).add('gm-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.quality(80) .quality(80)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -186,17 +186,17 @@ async.series({
// tfjs // tfjs
tfjs && jpegSuite.add('tfjs-node-buffer-buffer', { tfjs && jpegSuite.add('tfjs-node-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
const decoded = tfjs.node.decodeJpeg(inputJpgBuffer); const decoded = tfjs.node.decodeJpeg(inputJpgBuffer);
const resized = tfjs.image.resizeBilinear(decoded, [height, width]); const resized = tfjs.image.resizeBilinear(decoded, [height, width]);
tfjs tfjs
.node .node
.encodeJpeg(resized, 'rgb', 80) .encodeJpeg(resized, 'rgb', 80)
.then(function () { .then(() => {
deferred.resolve(); deferred.resolve();
tfjs.disposeVariables(); tfjs.disposeVariables();
}) })
.catch(function (err) { .catch((err) => {
throw err; throw err;
}); });
} }
@@ -204,10 +204,10 @@ async.series({
// sharp // sharp
jpegSuite.add('sharp-buffer-file', { jpegSuite.add('sharp-buffer-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.toFile(outputJpg, function (err) { .toFile(outputJpg, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -217,10 +217,10 @@ async.series({
} }
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -228,12 +228,25 @@ async.series({
} }
}); });
} }
}).add('sharp-buffer-uint8array', {
defer: true,
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.toUint8Array()
.then(() => {
deferred.resolve();
})
.catch((err) => {
throw err;
});
}
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.toFile(outputJpg, function (err) { .toFile(outputJpg, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -243,10 +256,10 @@ async.series({
} }
}).add('sharp-stream-stream', { }).add('sharp-stream-stream', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
const readable = fs.createReadStream(fixtures.inputJpg); const readable = fs.createReadStream(fixtures.inputJpg);
const writable = fs.createWriteStream(outputJpg); const writable = fs.createWriteStream(outputJpg);
writable.on('finish', function () { writable.on('finish', () => {
deferred.resolve(); deferred.resolve();
}); });
const pipeline = sharp() const pipeline = sharp()
@@ -255,10 +268,10 @@ async.series({
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -266,36 +279,49 @@ async.series({
} }
}); });
} }
}).add('sharp-promise', { }).add('sharp-file-uint8array', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(fixtures.inputJpg)
.resize(width, height) .resize(width, height)
.toBuffer() .toUint8Array()
.then(function () { .then(() => {
deferred.resolve(); deferred.resolve();
}) })
.catch(function (err) { .catch((err) => {
throw err; throw err;
}); });
} }
}).on('cycle', function (event) { }).add('sharp-promise', {
console.log('jpeg ' + String(event.target)); defer: true,
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer()
.then(() => {
deferred.resolve();
})
.catch((err) => {
throw err;
});
}
}).on('cycle', (event) => {
console.log(`jpeg ${String(event.target)}`);
}).on('complete', function () { }).on('complete', function () {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// Effect of applying operations // Effect of applying operations
operations: function (callback) { operations: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
const operationsSuite = new Benchmark.Suite('operations'); const operationsSuite = new Benchmark.Suite('operations');
operationsSuite.add('sharp-sharpen-mild', { operationsSuite.add('sharp-sharpen-mild', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.sharpen() .sharpen()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -305,11 +331,11 @@ async.series({
} }
}).add('sharp-sharpen-radius', { }).add('sharp-sharpen-radius', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.sharpen(3, 1, 3) .sharpen(3, 1, 3)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -319,11 +345,11 @@ async.series({
} }
}).add('sharp-blur-mild', { }).add('sharp-blur-mild', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.blur() .blur()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -333,11 +359,11 @@ async.series({
} }
}).add('sharp-blur-radius', { }).add('sharp-blur-radius', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.blur(3) .blur(3)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -347,11 +373,11 @@ async.series({
} }
}).add('sharp-gamma', { }).add('sharp-gamma', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.gamma() .gamma()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -361,11 +387,11 @@ async.series({
} }
}).add('sharp-normalise', { }).add('sharp-normalise', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.normalise() .normalise()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -375,11 +401,11 @@ async.series({
} }
}).add('sharp-greyscale', { }).add('sharp-greyscale', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.greyscale() .greyscale()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -389,12 +415,12 @@ async.series({
} }
}).add('sharp-greyscale-gamma', { }).add('sharp-greyscale-gamma', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.gamma() .gamma()
.greyscale() .greyscale()
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -404,11 +430,11 @@ async.series({
} }
}).add('sharp-progressive', { }).add('sharp-progressive', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.jpeg({ progressive: true }) .jpeg({ progressive: true })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -418,11 +444,11 @@ async.series({
} }
}).add('sharp-without-chroma-subsampling', { }).add('sharp-without-chroma-subsampling', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.jpeg({ chromaSubsampling: '4:4:4' }) .jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -432,11 +458,11 @@ async.series({
} }
}).add('sharp-rotate', { }).add('sharp-rotate', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.rotate(90) .rotate(90)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -446,11 +472,11 @@ async.series({
} }
}).add('sharp-without-simd', { }).add('sharp-without-simd', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp.simd(false); sharp.simd(false);
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
sharp.simd(true); sharp.simd(true);
if (err) { if (err) {
throw err; throw err;
@@ -461,10 +487,10 @@ async.series({
} }
}).add('sharp-random-access-read', { }).add('sharp-random-access-read', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer, { sequentialRead: false }) sharp(inputJpgBuffer, { sequentialRead: false })
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -474,13 +500,13 @@ async.series({
} }
}).add('sharp-crop-entropy', { }).add('sharp-crop-entropy', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { .resize(width, height, {
fit: 'cover', fit: 'cover',
position: sharp.strategy.entropy position: sharp.strategy.entropy
}) })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -490,13 +516,13 @@ async.series({
} }
}).add('sharp-crop-attention', { }).add('sharp-crop-attention', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { .resize(width, height, {
fit: 'cover', fit: 'cover',
position: sharp.strategy.attention position: sharp.strategy.attention
}) })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -504,21 +530,21 @@ async.series({
} }
}); });
} }
}).on('cycle', function (event) { }).on('cycle', (event) => {
console.log('operations ' + String(event.target)); console.log(`operations ${String(event.target)}`);
}).on('complete', function () { }).on('complete', function () {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// Comparative speed of kernels // Comparative speed of kernels
kernels: function (callback) { kernels: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('kernels')).add('sharp-cubic', { (new Benchmark.Suite('kernels')).add('sharp-cubic', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'cubic' }) .resize(width, height, { kernel: 'cubic' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -528,10 +554,10 @@ async.series({
} }
}).add('sharp-lanczos2', { }).add('sharp-lanczos2', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos2' }) .resize(width, height, { kernel: 'lanczos2' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -541,10 +567,10 @@ async.series({
} }
}).add('sharp-lanczos3', { }).add('sharp-lanczos3', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos3' }) .resize(width, height, { kernel: 'lanczos3' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -554,10 +580,10 @@ async.series({
} }
}).add('sharp-mks2013', { }).add('sharp-mks2013', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2013' }) .resize(width, height, { kernel: 'mks2013' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -567,10 +593,10 @@ async.series({
} }
}).add('sharp-mks2021', { }).add('sharp-mks2021', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputJpgBuffer) sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2021' }) .resize(width, height, { kernel: 'mks2021' })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -578,21 +604,21 @@ async.series({
} }
}); });
} }
}).on('cycle', function (event) { }).on('cycle', (event) => {
console.log('kernels ' + String(event.target)); console.log(`kernels ${String(event.target)}`);
}).on('complete', function () { }).on('complete', function () {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// PNG // PNG
png: function (callback) { png: (callback) => {
const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge); const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png'); const pngSuite = new Benchmark.Suite('png');
const minSamples = 64; const minSamples = 64;
// jimp // jimp
pngSuite.add('jimp-buffer-buffer', { pngSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
fn: async function (deferred) { fn: async (deferred) => {
const image = await Jimp.read(inputPngBuffer); const image = await Jimp.read(inputPngBuffer);
await image await image
.resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC }) .resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC })
@@ -601,7 +627,7 @@ async.series({
} }
}).add('jimp-file-file', { }).add('jimp-file-file', {
defer: true, defer: true,
fn: async function (deferred) { fn: async (deferred) => {
const image = await Jimp.read(fixtures.inputPngAlphaPremultiplicationLarge); const image = await Jimp.read(fixtures.inputPngAlphaPremultiplicationLarge);
await image await image
.resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC }) .resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC })
@@ -612,18 +638,18 @@ async.series({
// mapnik // mapnik
mapnik && pngSuite.add('mapnik-file-file', { mapnik && pngSuite.add('mapnik-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) { mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, (err, img) => {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply((err, img) => {
if (err) throw err; if (err) throw err;
img.resize(width, heightPng, { img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) { }, (err, img) => {
if (err) throw err; if (err) throw err;
img.demultiply(function (err, img) { img.demultiply((err, img) => {
if (err) throw err; if (err) throw err;
img.save(outputPng, 'png32:f=no:z=6', function (err) { img.save(outputPng, 'png32:f=no:z=6', (err) => {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -634,18 +660,18 @@ async.series({
} }
}).add('mapnik-buffer-buffer', { }).add('mapnik-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, function (err, img) { mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, (err, img) => {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply((err, img) => {
if (err) throw err; if (err) throw err;
img.resize(width, heightPng, { img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) { }, (err, img) => {
if (err) throw err; if (err) throw err;
img.demultiply(function (err, img) { img.demultiply((err, img) => {
if (err) throw err; if (err) throw err;
img.encode('png32:f=no:z=6', function (err) { img.encode('png32:f=no:z=6', (err) => {
if (err) throw err; if (err) throw err;
deferred.resolve(); deferred.resolve();
}); });
@@ -658,7 +684,7 @@ async.series({
// imagemagick // imagemagick
pngSuite.add('imagemagick-file-file', { pngSuite.add('imagemagick-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputPngAlphaPremultiplicationLarge, srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: outputPng, dstPath: outputPng,
@@ -669,7 +695,7 @@ async.series({
'-define', 'PNG:compression-level=6', '-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0' '-define', 'PNG:compression-filter=0'
] ]
}, function (err) { }, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -681,13 +707,13 @@ async.series({
// gm // gm
pngSuite.add('gm-file-file', { pngSuite.add('gm-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(fixtures.inputPngAlphaPremultiplicationLarge) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, heightPng) .resize(width, heightPng)
.define('PNG:compression-level=6') .define('PNG:compression-level=6')
.define('PNG:compression-filter=0') .define('PNG:compression-filter=0')
.write(outputPng, function (err) { .write(outputPng, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -697,13 +723,13 @@ async.series({
} }
}).add('gm-file-buffer', { }).add('gm-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(fixtures.inputPngAlphaPremultiplicationLarge) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, heightPng) .resize(width, heightPng)
.define('PNG:compression-level=6') .define('PNG:compression-level=6')
.define('PNG:compression-filter=0') .define('PNG:compression-filter=0')
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -716,11 +742,11 @@ async.series({
pngSuite.add('sharp-buffer-file', { pngSuite.add('sharp-buffer-file', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 6 }) .png({ compressionLevel: 6 })
.toFile(outputPng, function (err) { .toFile(outputPng, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -731,11 +757,11 @@ async.series({
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 6 }) .png({ compressionLevel: 6 })
.toBuffer(function (err, data) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -746,11 +772,11 @@ async.series({
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputPngAlphaPremultiplicationLarge) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 6 }) .png({ compressionLevel: 6 })
.toFile(outputPng, function (err) { .toFile(outputPng, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -761,11 +787,11 @@ async.series({
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputPngAlphaPremultiplicationLarge) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 6 }) .png({ compressionLevel: 6 })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -776,11 +802,11 @@ async.series({
}).add('sharp-progressive', { }).add('sharp-progressive', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 6, progressive: true }) .png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -791,11 +817,11 @@ async.series({
}).add('sharp-adaptiveFiltering', { }).add('sharp-adaptiveFiltering', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, heightPng) .resize(width, heightPng)
.png({ adaptiveFiltering: true, compressionLevel: 6 }) .png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -806,11 +832,11 @@ async.series({
}).add('sharp-compressionLevel=9', { }).add('sharp-compressionLevel=9', {
defer: true, defer: true,
minSamples, minSamples,
fn: function (deferred) { fn: (deferred) => {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, heightPng) .resize(width, heightPng)
.png({ compressionLevel: 9 }) .png({ compressionLevel: 9 })
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -819,21 +845,21 @@ async.series({
}); });
} }
}); });
pngSuite.on('cycle', function (event) { pngSuite.on('cycle', (event) => {
console.log(' png ' + String(event.target)); console.log(` png ${String(event.target)}`);
}).on('complete', function () { }).on('complete', function () {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
}, },
// WebP // WebP
webp: function (callback) { webp: (callback) => {
const inputWebPBuffer = fs.readFileSync(fixtures.inputWebP); const inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
(new Benchmark.Suite('webp')).add('sharp-buffer-file', { (new Benchmark.Suite('webp')).add('sharp-buffer-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputWebPBuffer) sharp(inputWebPBuffer)
.resize(width, height) .resize(width, height)
.toFile(outputWebP, function (err) { .toFile(outputWebP, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -843,10 +869,10 @@ async.series({
} }
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(inputWebPBuffer) sharp(inputWebPBuffer)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -856,10 +882,10 @@ async.series({
} }
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputWebP) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.toFile(outputWebP, function (err) { .toFile(outputWebP, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -869,10 +895,10 @@ async.series({
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputWebP) sharp(fixtures.inputWebP)
.resize(width, height) .resize(width, height)
.toBuffer(function (err) { .toBuffer((err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -880,19 +906,19 @@ async.series({
} }
}); });
} }
}).on('cycle', function (event) { }).on('cycle', (event) => {
console.log('webp ' + String(event.target)); console.log(`webp ${String(event.target)}`);
}).on('complete', function () { }).on('complete', function () {
callback(null, this.filter('fastest').map('name')); callback(null, this.filter('fastest').map('name'));
}).run(); }).run();
} }
}, function (err, results) { }, (err, results) => {
if (err) { if (err) {
throw err; throw err;
} }
Object.keys(results).forEach(function (format) { Object.keys(results).forEach((format) => {
if (results[format].toString().substr(0, 5) !== 'sharp') { if (results[format].toString().substr(0, 5) !== 'sharp') {
console.log('sharp was slower than ' + results[format] + ' for ' + format); console.log(`sharp was slower than ${results[format]} for ${format}`);
} }
}); });
console.dir(sharp.cache()); console.dir(sharp.cache());

View File

@@ -1,11 +1,11 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
'use strict'; */
const imagemagick = require('imagemagick'); const imagemagick = require('imagemagick');
const gm = require('gm'); const gm = require('gm');
const assert = require('assert'); const assert = require('node:assert');
const Benchmark = require('benchmark'); const Benchmark = require('benchmark');
const sharp = require('../../'); const sharp = require('../../');
@@ -16,13 +16,11 @@ sharp.cache(false);
const min = 320; const min = 320;
const max = 960; const max = 960;
const randomDimension = function () { const randomDimension = () => Math.ceil((Math.random() * (max - min)) + min);
return Math.ceil((Math.random() * (max - min)) + min);
};
new Benchmark.Suite('random').add('imagemagick', { new Benchmark.Suite('random').add('imagemagick', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputJpg, srcPath: fixtures.inputJpg,
dstPath: fixtures.path('output.jpg'), dstPath: fixtures.path('output.jpg'),
@@ -31,7 +29,7 @@ new Benchmark.Suite('random').add('imagemagick', {
height: randomDimension(), height: randomDimension(),
format: 'jpg', format: 'jpg',
filter: 'Lanczos' filter: 'Lanczos'
}, function (err) { }, (err) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -41,12 +39,12 @@ new Benchmark.Suite('random').add('imagemagick', {
} }
}).add('gm', { }).add('gm', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
gm(fixtures.inputJpg) gm(fixtures.inputJpg)
.resize(randomDimension(), randomDimension()) .resize(randomDimension(), randomDimension())
.filter('Lanczos') .filter('Lanczos')
.quality(80) .quality(80)
.toBuffer(function (err, buffer) { .toBuffer((err, buffer) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -57,10 +55,10 @@ new Benchmark.Suite('random').add('imagemagick', {
} }
}).add('sharp', { }).add('sharp', {
defer: true, defer: true,
fn: function (deferred) { fn: (deferred) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(randomDimension(), randomDimension()) .resize(randomDimension(), randomDimension())
.toBuffer(function (err, buffer) { .toBuffer((err, buffer) => {
if (err) { if (err) {
throw err; throw err;
} else { } else {
@@ -69,9 +67,9 @@ new Benchmark.Suite('random').add('imagemagick', {
} }
}); });
} }
}).on('cycle', function (event) { }).on('cycle', (event) => {
console.log(String(event.target)); console.log(String(event.target));
}).on('complete', function () { }).on('complete', function () {
const winner = this.filter('fastest').map('name'); const winner = this.filter('fastest').map('name');
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner); assert.strictEqual('sharp', String(winner), `sharp was slower than ${winner}`);
}).run(); }).run();

BIN
test/fixtures/bonne.geo.tif vendored Normal file

Binary file not shown.

BIN
test/fixtures/gain-map.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,16 +1,14 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const path = require('node:path');
const path = require('path');
const sharp = require('../../'); const sharp = require('../../');
const maxColourDistance = require('../../lib/sharp')._maxColourDistance; const maxColourDistance = require('../../lib/sharp')._maxColourDistance;
// Helpers // Helpers
const getPath = function (filename) { const getPath = (filename) => path.join(__dirname, filename);
return path.join(__dirname, 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
@@ -22,7 +20,7 @@ async function fingerprint (image) {
.resize(9, 8, { fit: sharp.fit.fill }) .resize(9, 8, { fit: sharp.fit.fill })
.raw() .raw()
.toBuffer() .toBuffer()
.then(function (data) { .then((data) => {
let fingerprint = ''; let fingerprint = '';
for (let col = 0; col < 8; col++) { for (let col = 0; col < 8; col++) {
for (let row = 0; row < 8; row++) { for (let row = 0; row < 8; row++) {
@@ -72,9 +70,11 @@ module.exports = {
inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg
inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg
inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz
inputJpgWithGainMap: getPath('gain-map.jpg'), // https://github.com/libvips/libvips/issues/3799
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngGradients: getPath('gradients-rgb8.png'), inputPngGradients: getPath('gradients-rgb8.png'),
inputPngWithSlightGradientBorder: getPath('slight-gradient-border.png'),
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngCompleteTransparency: getPath('full-transparent.png'), inputPngCompleteTransparency: getPath('full-transparent.png'),
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'), inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
@@ -115,6 +115,7 @@ module.exports = {
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 inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045
inputTiffGeo: getPath('bonne.geo.tif'), // https://download.osgeo.org/geotiff/samples/intergraph
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2 inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
inputJp2TileParts: getPath('relax_tileparts.jp2'), // kdu_expand -i relax.jp2 -o relax-tmp.tif ; kdu_compress -i relax-tmp.tif -o relax_tileparts.jp2 -jp2_space sRGB Clayers=8 -rate 1.0,0.04 Stiles='{128,128}' ORGtparts=L ; rm relax-tmp.tif inputJp2TileParts: getPath('relax_tileparts.jp2'), // kdu_expand -i relax.jp2 -o relax-tmp.tif ; kdu_compress -i relax-tmp.tif -o relax_tileparts.jp2 -jp2_space sRGB Clayers=8 -rate 1.0,0.04 Stiles='{128,128}' ORGtparts=L ; rm relax-tmp.tif
@@ -147,14 +148,12 @@ module.exports = {
path: getPath, path: getPath,
// Path for expected output images // Path for expected output images
expected: function (filename) { expected: (filename) => getPath(path.join('expected', filename)),
return getPath(path.join('expected', filename));
},
// 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: async function (expectedImage, actualImage, options, callback) { assertSimilar: async (expectedImage, actualImage, options, callback) => {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;
options = {}; options = {};
@@ -194,12 +193,12 @@ module.exports = {
} }
}, },
assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) { assertMaxColourDistance: (actualImagePath, expectedImagePath, acceptedDistance) => {
if (typeof actualImagePath !== 'string') { if (typeof actualImagePath !== 'string') {
throw new TypeError('`actualImagePath` must be a string; got ' + actualImagePath); throw new TypeError(`\`actualImagePath\` must be a string; got ${actualImagePath}`);
} }
if (typeof expectedImagePath !== 'string') { if (typeof expectedImagePath !== 'string') {
throw new TypeError('`expectedImagePath` must be a string; got ' + expectedImagePath); throw new TypeError(`\`expectedImagePath\` must be a string; got ${expectedImagePath}`);
} }
if (typeof acceptedDistance !== 'number') { if (typeof acceptedDistance !== 'number') {
// Default threshold // Default threshold
@@ -207,7 +206,7 @@ module.exports = {
} }
const distance = maxColourDistance(actualImagePath, expectedImagePath); const distance = maxColourDistance(actualImagePath, expectedImagePath);
if (distance > acceptedDistance) { if (distance > acceptedDistance) {
throw new Error('Expected maximum absolute distance of ' + acceptedDistance + ', actual ' + distance); throw new Error(`Expected maximum absolute distance of ${acceptedDistance}, actual ${distance}`);
} }
} }

BIN
test/fixtures/slight-gradient-border.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -18,5 +18,5 @@ for test in $TESTS; do
--show-leak-kinds=definite,indirect \ --show-leak-kinds=definite,indirect \
--num-callers=20 \ --num-callers=20 \
--trace-children=yes \ --trace-children=yes \
node --expose-gc --zero-fill-buffers node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "test/unit/$test"; node --zero-fill-buffers --test "test/unit/$test";
done done

View File

@@ -1,6 +1,9 @@
// biome-ignore-all lint/correctness/noUnusedFunctionParameters: types only test file
// biome-ignore-all lint/correctness/noUnusedVariables: types only test file
import sharp = require('../../'); import sharp = require('../../');
import { createReadStream, createWriteStream } from 'fs'; import { createReadStream, createWriteStream } from 'node:fs';
const input: Buffer = Buffer.alloc(0); const input: Buffer = Buffer.alloc(0);
const readableStream: NodeJS.ReadableStream = createReadStream(input); const readableStream: NodeJS.ReadableStream = createReadStream(input);
@@ -79,10 +82,13 @@ sharp({
let transformer = sharp() let transformer = sharp()
.resize(300) .resize(300)
.on('info', (info: sharp.OutputInfo) => { .on('info', (info: sharp.OutputInfo) => {
console.log('Image height is ' + info.height); console.log(`Image height is ${info.height}`);
}); });
readableStream.pipe(transformer).pipe(writableStream); readableStream.pipe(transformer).pipe(writableStream);
sharp().toUint8Array();
sharp().toUint8Array().then(({ data }) => data.byteLength);
console.log(sharp.format); console.log(sharp.format);
console.log(sharp.versions); console.log(sharp.versions);
@@ -228,7 +234,7 @@ sharp(input)
sharp(input) sharp(input)
.resize(100, 100) .resize(100, 100)
.toFormat('jpg') .toFormat('avif')
.toBuffer({ resolveWithObject: false }) .toBuffer({ resolveWithObject: false })
.then((outputBuffer: Buffer) => { .then((outputBuffer: Buffer) => {
// Resolves with a Buffer object when resolveWithObject is false // Resolves with a Buffer object when resolveWithObject is false
@@ -261,9 +267,7 @@ sharp(input)
// Output to tif // Output to tif
sharp(input) sharp(input)
.resize(100, 100) .resize(100, 100)
.toFormat('tif')
.toFormat('tiff') .toFormat('tiff')
.toFormat(sharp.format.tif)
.toFormat(sharp.format.tiff) .toFormat(sharp.format.tiff)
.toBuffer(); .toBuffer();
@@ -318,7 +322,7 @@ sharp('input.gif')
// From https://sharp.pixelplumbing.com/api-output#examples-9 // From https://sharp.pixelplumbing.com/api-output#examples-9
// Extract raw RGB pixel data from JPEG input // Extract raw RGB pixel data from JPEG input
sharp('input.jpg') sharp('input.jpg')
.raw() .raw({ depth: 'ushort' })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
console.log(data); console.log(data);
@@ -359,7 +363,7 @@ sharp(input)
.avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' }) .avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
.heif() .heif()
.heif({}) .heif({})
.heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0' }) .heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0', tune: 'psnr' })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
console.log(data); console.log(data);
@@ -542,8 +546,8 @@ sharp('input.tiff').jxl({ decodingTier: 4 }).toFile('out.jxl');
sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl'); sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl');
sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl'); sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl');
// Support `minSize` and `mixed` webp options // Support webp options
sharp('input.tiff').webp({ minSize: true, mixed: true }).toFile('out.gif'); sharp('input.tiff').webp({ minSize: true, mixed: true, exact: true }).toFile('out.webp');
// 'failOn' input param // 'failOn' input param
sharp('input.tiff', { failOn: 'none' }); sharp('input.tiff', { failOn: 'none' });
@@ -595,7 +599,7 @@ const vertexSplitQuadraticBasisSpline: string = sharp.interpolators.vertexSplitQ
// Triming // Triming
sharp(input).trim({ background: '#000' }).toBuffer(); sharp(input).trim({ background: '#000' }).toBuffer();
sharp(input).trim({ threshold: 10, lineArt: true }).toBuffer(); sharp(input).trim({ threshold: 10, lineArt: true }).toBuffer();
sharp(input).trim({ background: '#bf1942', threshold: 30 }).toBuffer(); sharp(input).trim({ background: '#bf1942', threshold: 30, margin: 20 }).toBuffer();
// Text input // Text input
sharp({ sharp({
@@ -768,3 +772,35 @@ sharp().erode();
sharp().erode(1); sharp().erode(1);
sharp().dilate(); sharp().dilate();
sharp().dilate(1); sharp().dilate(1);
sharp.format.dcraw;
sharp.format.dz;
sharp.format.fits;
sharp.format.gif;
sharp.format.heif;
sharp.format.jp2;
sharp.format.jpeg;
sharp.format.jxl;
sharp.format.magick;
sharp.format.openslide;
sharp.format.pdf;
sharp.format.png;
sharp.format.ppm;
sharp.format.rad;
sharp.format.raw;
sharp.format.svg;
sharp.format.tiff;
sharp.format.vips;
sharp.format.webp;
// @ts-expect-error
sharp.format.avif;
// @ts-expect-error
sharp.format.input;
// @ts-expect-error
sharp.format.jp2k;
// @ts-expect-error
sharp.format.jpg;
// @ts-expect-error
sharp.format.tif;
// @ts-expect-error
sharp.format.v;

16
test/unit.mjs Normal file
View File

@@ -0,0 +1,16 @@
import { readdir } from 'node:fs/promises';
import { run } from 'node:test';
import { spec } from 'node:test/reporters';
const files = (await readdir('./test/unit')).map((f) => `./test/unit/${f}`);
run({
files,
concurrency: true,
timeout: 60000,
coverage: true,
coverageIncludeGlobs: ['lib/*.js'],
branchCoverage: 100,
})
.compose(new spec())
.pipe(process.stdout);

View File

@@ -1,9 +1,10 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');

View File

@@ -1,18 +1,19 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
const sharp = require('../../'); const sharp = require('../../');
describe('Alpha transparency', function () { describe('Alpha transparency', () => {
it('Flatten to black', function (done) { it('Flatten to black', (_t, done) => {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.flatten() .flatten()
.resize(400, 300) .resize(400, 300)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(400, info.width); assert.strictEqual(400, info.width);
assert.strictEqual(300, info.height); assert.strictEqual(300, info.height);
@@ -20,14 +21,14 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Flatten to RGB orange', function (done) { it('Flatten to RGB orange', (_t, done) => {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.resize(400, 300) .resize(400, 300)
.flatten({ .flatten({
background: { r: 255, g: 102, b: 0 } background: { r: 255, g: 102, b: 0 }
}) })
.jpeg({ chromaSubsampling: '4:4:4' }) .jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(400, info.width); assert.strictEqual(400, info.width);
assert.strictEqual(300, info.height); assert.strictEqual(300, info.height);
@@ -35,12 +36,12 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Flatten to CSS/hex orange', function (done) { it('Flatten to CSS/hex orange', (_t, done) => {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.resize(400, 300) .resize(400, 300)
.flatten({ background: '#ff6600' }) .flatten({ background: '#ff6600' })
.jpeg({ chromaSubsampling: '4:4:4' }) .jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(400, info.width); assert.strictEqual(400, info.width);
assert.strictEqual(300, info.height); assert.strictEqual(300, info.height);
@@ -48,13 +49,13 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Flatten 16-bit PNG with transparency to orange', function (done) { it('Flatten 16-bit PNG with transparency to orange', (_t, done) => {
const output = fixtures.path('output.flatten-rgb16-orange.jpg'); const output = fixtures.path('output.flatten-rgb16-orange.jpg');
sharp(fixtures.inputPngWithTransparency16bit) sharp(fixtures.inputPngWithTransparency16bit)
.flatten({ .flatten({
background: { r: 255, g: 102, b: 0 } background: { r: 255, g: 102, b: 0 }
}) })
.toFile(output, function (err, info) { .toFile(output, (err, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width); assert.strictEqual(32, info.width);
@@ -64,10 +65,10 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Do not flatten', function (done) { it('Do not flatten', (_t, done) => {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.flatten(false) .flatten(false)
.toBuffer(function (err, data, info) { .toBuffer((err, _data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
@@ -75,10 +76,10 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Ignored for JPEG', function (done) { it('Ignored for JPEG', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.flatten({ background: '#ff0000' }) .flatten({ background: '#ff0000' })
.toBuffer(function (err, data, info) { .toBuffer((err, _data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
@@ -98,68 +99,60 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function () { it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', () => {
const base = 'alpha-premultiply-enlargement-2048x1536-paper.png'; const base = 'alpha-premultiply-enlargement-2048x1536-paper.png';
const actual = fixtures.path('output.' + base); const actual = fixtures.path(`output.${base}`);
const expected = fixtures.expected(base); const expected = fixtures.expected(base);
return sharp(fixtures.inputPngAlphaPremultiplicationSmall) return sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536) .resize(2048, 1536)
.toFile(actual) .toFile(actual)
.then(function () { .then(() => {
fixtures.assertMaxColourDistance(actual, expected, 102); fixtures.assertMaxColourDistance(actual, expected, 102);
}); });
}); });
it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', function () { it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', () => {
const base = 'alpha-premultiply-reduction-1024x768-paper.png'; const base = 'alpha-premultiply-reduction-1024x768-paper.png';
const actual = fixtures.path('output.' + base); const actual = fixtures.path(`output.${base}`);
const expected = fixtures.expected(base); const expected = fixtures.expected(base);
return sharp(fixtures.inputPngAlphaPremultiplicationLarge) return sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768) .resize(1024, 768)
.toFile(actual) .toFile(actual)
.then(function () { .then(() => {
fixtures.assertMaxColourDistance(actual, expected, 102); fixtures.assertMaxColourDistance(actual, expected, 102);
}); });
}); });
it('Removes alpha from fixtures with transparency, ignores those without', function () { it('Removes alpha from fixtures with transparency, ignores those without', () => Promise.all([
return Promise.all([
fixtures.inputPngWithTransparency, fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit, fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency, fixtures.inputWebPWithTransparency,
fixtures.inputJpg, fixtures.inputJpg,
fixtures.inputPng, fixtures.inputPng,
fixtures.inputWebP fixtures.inputWebP
].map(function (input) { ].map((input) => sharp(input)
return sharp(input)
.resize(10) .resize(10)
.removeAlpha() .removeAlpha()
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(function (result) { .then((result) => {
assert.strictEqual(3, result.info.channels); assert.strictEqual(3, result.info.channels);
}); }))));
}));
});
it('Ensures alpha from fixtures without transparency, ignores those with', function () { it('Ensures alpha from fixtures without transparency, ignores those with', () => Promise.all([
return Promise.all([
fixtures.inputPngWithTransparency, fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit, fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency, fixtures.inputWebPWithTransparency,
fixtures.inputJpg, fixtures.inputJpg,
fixtures.inputPng, fixtures.inputPng,
fixtures.inputWebP fixtures.inputWebP
].map(function (input) { ].map((input) => sharp(input)
return sharp(input)
.resize(10) .resize(10)
.ensureAlpha() .ensureAlpha()
.png() .png()
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(function (result) { .then((result) => {
assert.strictEqual(4, result.info.channels); assert.strictEqual(4, result.info.channels);
}); }))));
}));
});
it('Valid ensureAlpha value used for alpha channel', async () => { it('Valid ensureAlpha value used for alpha channel', async () => {
const background = { r: 255, g: 0, b: 0 }; const background = { r: 255, g: 0, b: 0 };

View File

@@ -1,9 +1,10 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures'); const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures');
@@ -20,8 +21,8 @@ describe('AVIF', () => {
.resize(32) .resize(32)
.jpeg() .jpeg()
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data) const { size, ...metadata } = await sharp(data).metadata();
.metadata(); void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 13, height: 13,
@@ -49,8 +50,8 @@ describe('AVIF', () => {
.resize(32) .resize(32)
.avif({ effort: 0 }) .avif({ effort: 0 })
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data) const { size, ...metadata } = await sharp(data).metadata();
.metadata(); void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 26, height: 26,
@@ -77,8 +78,8 @@ describe('AVIF', () => {
const data = await sharp(inputAvif) const data = await sharp(inputAvif)
.resize(32) .resize(32)
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data) const { size, ...metadata } = await sharp(data).metadata();
.metadata(); void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 13, height: 13,
@@ -106,8 +107,8 @@ describe('AVIF', () => {
.resize(10) .resize(10)
.avif({ effort: 0 }) .avif({ effort: 0 })
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data) const { size, ...metadata } = await sharp(data).metadata();
.metadata(); void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 300, height: 300,
@@ -136,8 +137,8 @@ describe('AVIF', () => {
.sharpen() .sharpen()
.avif({ effort: 0 }) .avif({ effort: 0 })
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data) const { size, ...metadata } = await sharp(data).metadata();
.metadata(); void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 26, height: 26,
@@ -174,9 +175,22 @@ describe('AVIF', () => {
) )
); );
it('Invalid bitdepth value throws error', async () => { it('Invalid bitdepth value throws error', () =>
assert.rejects( assert.throws(
() => sharp().avif({ bitdepth: 11 }), () => sharp().avif({ bitdepth: 11 }),
/Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/); /Expected 8, 10 or 12 for bitdepth but received 11 of type number/
}); )
);
it('Different tune options result in different file sizes', async () => {
const ssim = await sharp(inputJpg)
.resize(32)
.avif({ tune: 'ssim', effort: 0 })
.toBuffer();
const iq = await sharp(inputJpg)
.resize(32)
.avif({ tune: 'iq', effort: 0 })
.toBuffer();
assert(ssim.length < iq.length);
})
}); });

View File

@@ -1,51 +1,52 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
const sharp = require('../../'); const sharp = require('../../');
describe('Bandbool per-channel boolean operations', function () { describe('Bandbool per-channel boolean operations', () => {
[ [
sharp.bool.and, sharp.bool.and,
sharp.bool.or, sharp.bool.or,
sharp.bool.eor sharp.bool.eor
] ]
.forEach(function (op) { .forEach((op) => {
it(op + ' operation', function (done) { it(`${op} operation`, (_t, done) => {
sharp(fixtures.inputPngBooleanNoAlpha) sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool(op) .bandbool(op)
.toColourspace('b-w') .toColourspace('b-w')
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(200, info.width); assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height); assert.strictEqual(200, info.height);
assert.strictEqual(1, info.channels); assert.strictEqual(1, info.channels);
fixtures.assertSimilar(fixtures.expected('bandbool_' + op + '_result.png'), data, done); fixtures.assertSimilar(fixtures.expected(`bandbool_${op}_result.png`), data, done);
}); });
}); });
}); });
it('sRGB image retains 3 channels', function (done) { it('sRGB image retains 3 channels', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.bandbool('and') .bandbool('and')
.toBuffer(function (err, data, info) { .toBuffer((err, _data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
done(); done();
}); });
}); });
it('Invalid operation', function () { it('Invalid operation', () => {
assert.throws(function () { assert.throws(() => {
sharp().bandbool('fail'); sharp().bandbool('fail');
}); });
}); });
it('Missing operation', function () { it('Missing operation', () => {
assert.throws(function () { assert.throws(() => {
sharp().bandbool(); sharp().bandbool();
}); });
}); });

View File

@@ -1,19 +1,20 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Blur', function () { describe('Blur', () => {
it('specific radius 1', function (done) { it('specific radius 1', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur(1) .blur(1)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -22,11 +23,11 @@ describe('Blur', function () {
}); });
}); });
it('specific radius 10', function (done) { it('specific radius 10', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur(10) .blur(10)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -35,11 +36,11 @@ describe('Blur', function () {
}); });
}); });
it('specific options.sigma 10', function (done) { it('specific options.sigma 10', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur({ sigma: 10 }) .blur({ sigma: 10 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -48,11 +49,11 @@ describe('Blur', function () {
}); });
}); });
it('specific radius 0.3', function (done) { it('specific radius 0.3', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur(0.3) .blur(0.3)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -61,11 +62,11 @@ describe('Blur', function () {
}); });
}); });
it('mild blur', function (done) { it('mild blur', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur() .blur()
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -74,17 +75,17 @@ describe('Blur', function () {
}); });
}); });
it('invalid radius', function () { it('invalid radius', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).blur(0.1); sharp(fixtures.inputJpg).blur(0.1);
}); });
}); });
it('blurred image is smaller than non-blurred', function (done) { it('blurred image is smaller than non-blurred', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur(false) .blur(false)
.toBuffer(function (err, notBlurred, info) { .toBuffer((err, notBlurred, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, notBlurred.length > 0); assert.strictEqual(true, notBlurred.length > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
@@ -93,7 +94,7 @@ describe('Blur', function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.blur(true) .blur(true)
.toBuffer(function (err, blurred, info) { .toBuffer((err, blurred, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, blurred.length > 0); assert.strictEqual(true, blurred.length > 0);
assert.strictEqual(true, blurred.length < notBlurred.length); assert.strictEqual(true, blurred.length < notBlurred.length);
@@ -105,18 +106,18 @@ describe('Blur', function () {
}); });
}); });
it('invalid precision', function () { it('invalid precision', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' }); sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' });
}, /Expected one of: integer, float, approximate for precision but received invalid of type string/); }, /Expected one of: integer, float, approximate for precision but received invalid of type string/);
}); });
it('invalid minAmplitude', function () { it('invalid minAmplitude', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 }); sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 });
}, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/); }, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/);
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 1.01 }); 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/); }, /Expected number between 0.001 and 1 for minAmplitude but received 1.01 of type number/);
}); });
@@ -149,8 +150,8 @@ describe('Blur', function () {
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow); await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow);
}); });
it('options.sigma is required if options object is passed', function () { it('options.sigma is required if options object is passed', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).blur({ precision: 'invalid' }); sharp(fixtures.inputJpg).blur({ precision: 'invalid' });
}, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/); }, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/);
}); });

View File

@@ -1,15 +1,16 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const fs = require('node:fs');
const { describe, it } = require('node:test');
const fs = require('fs'); const assert = require('node:assert');
const assert = require('assert');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
const sharp = require('../../'); const sharp = require('../../');
describe('Boolean operation between two images', function () { describe('Boolean operation between two images', () => {
const inputJpgBooleanTestBuffer = fs.readFileSync(fixtures.inputJpgBooleanTest); const inputJpgBooleanTestBuffer = fs.readFileSync(fixtures.inputJpgBooleanTest);
[ [
@@ -17,63 +18,63 @@ describe('Boolean operation between two images', function () {
sharp.bool.or, sharp.bool.or,
sharp.bool.eor sharp.bool.eor
] ]
.forEach(function (op) { .forEach((op) => {
it(op + ' operation, file', function (done) { it(`${op} operation, file`, (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.boolean(fixtures.inputJpgBooleanTest, op) .boolean(fixtures.inputJpgBooleanTest, op)
.toBuffer(function (err, data, info) { .toBuffer((err, data, 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.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done);
}); });
}); });
it(op + ' operation, buffer', function (done) { it(`${op} operation, buffer`, (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.boolean(inputJpgBooleanTestBuffer, op) .boolean(inputJpgBooleanTestBuffer, op)
.toBuffer(function (err, data, info) { .toBuffer((err, data, 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.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done);
}); });
}); });
it(op + ' operation, raw', function (done) { it(`${op} operation, raw`, (_t, done) => {
sharp(fixtures.inputJpgBooleanTest) sharp(fixtures.inputJpgBooleanTest)
.raw() .raw()
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.boolean(data, op, { raw: info }) .boolean(data, op, { raw: info })
.toBuffer(function (err, data, info) { .toBuffer((err, data, 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.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done);
}); });
}); });
}); });
}); });
it('Invalid operation', function () { it('Invalid operation', () => {
assert.throws(function () { assert.throws(() => {
sharp().boolean(fixtures.inputJpgBooleanTest, 'fail'); sharp().boolean(fixtures.inputJpgBooleanTest, 'fail');
}); });
}); });
it('Invalid operation, non-string', function () { it('Invalid operation, non-string', () => {
assert.throws(function () { assert.throws(() => {
sharp().boolean(fixtures.inputJpgBooleanTest, null); sharp().boolean(fixtures.inputJpgBooleanTest, null);
}); });
}); });
it('Missing input', function () { it('Missing input', () => {
assert.throws(function () { assert.throws(() => {
sharp().boolean(); sharp().boolean();
}); });
}); });

View File

@@ -1,139 +1,140 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../lib'); const sharp = require('../../lib');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Clahe', function () { describe('Clahe', () => {
it('width 5 width 5 maxSlope 0', function (done) { it('width 5 width 5 maxSlope 0', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 5, height: 5, maxSlope: 0 }) .clahe({ width: 5, height: 5, maxSlope: 0 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-5-5-0.jpg'), data, { threshold: 10 }, done); fixtures.assertSimilar(fixtures.expected('clahe-5-5-0.jpg'), data, { threshold: 10 }, done);
}); });
}); });
it('width 5 width 5 maxSlope 5', function (done) { it('width 5 width 5 maxSlope 5', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 5, height: 5, maxSlope: 5 }) .clahe({ width: 5, height: 5, maxSlope: 5 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-5-5-5.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-5-5-5.jpg'), data, done);
}); });
}); });
it('width 11 width 25 maxSlope 14', function (done) { it('width 11 width 25 maxSlope 14', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 11, height: 25, maxSlope: 14 }) .clahe({ width: 11, height: 25, maxSlope: 14 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-11-25-14.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-11-25-14.jpg'), data, done);
}); });
}); });
it('width 50 width 50 maxSlope 0', function (done) { it('width 50 width 50 maxSlope 0', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 50, height: 50, maxSlope: 0 }) .clahe({ width: 50, height: 50, maxSlope: 0 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-50-50-0.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-50-50-0.jpg'), data, done);
}); });
}); });
it('width 50 width 50 maxSlope 14', function (done) { it('width 50 width 50 maxSlope 14', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 50, height: 50, maxSlope: 14 }) .clahe({ width: 50, height: 50, maxSlope: 14 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-50-50-14.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-50-50-14.jpg'), data, done);
}); });
}); });
it('width 100 width 50 maxSlope 3', function (done) { it('width 100 width 50 maxSlope 3', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 100, height: 50, maxSlope: 3 }) .clahe({ width: 100, height: 50, maxSlope: 3 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done);
}); });
}); });
it('width 100 width 100 maxSlope 0', function (done) { it('width 100 width 100 maxSlope 0', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 100, height: 100, maxSlope: 0 }) .clahe({ width: 100, height: 100, maxSlope: 0 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-100-100-0.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-100-100-0.jpg'), data, done);
}); });
}); });
it('invalid maxSlope', function () { it('invalid maxSlope', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: -5 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: -5 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 110 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 110 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 5.5 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 5.5 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 'a string' }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 'a string' });
}); });
}); });
it('invalid width', function () { it('invalid width', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100.5, height: 100 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100.5, height: 100 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: -5, height: 100 }); sharp(fixtures.inputJpgClahe).clahe({ width: -5, height: 100 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: true, height: 100 }); sharp(fixtures.inputJpgClahe).clahe({ width: true, height: 100 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 'string test', height: 100 }); sharp(fixtures.inputJpgClahe).clahe({ width: 'string test', height: 100 });
}); });
}); });
it('invalid height', function () { it('invalid height', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100.5 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100.5 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: -5 }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: -5 });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: true }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: true });
}); });
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 'string test' }); sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 'string test' });
}); });
}); });
it('invalid options object', function () { it('invalid options object', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe(100, 100, 5); sharp(fixtures.inputJpgClahe).clahe(100, 100, 5);
}); });
}); });
it('uses default maxSlope of 3', function (done) { it('uses default maxSlope of 3', (_t, done) => {
sharp(fixtures.inputJpgClahe) sharp(fixtures.inputJpgClahe)
.clahe({ width: 100, height: 50 }) .clahe({ width: 100, height: 50 })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done); fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done);

View File

@@ -1,29 +1,30 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const fs = require('node:fs');
const { afterEach, beforeEach, describe, it } = require('node:test');
const fs = require('fs'); const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Clone', function () { describe('Clone', () => {
beforeEach(function () { beforeEach(() => {
sharp.cache(false); sharp.cache(false);
}); });
afterEach(function () { afterEach(() => {
sharp.cache(true); sharp.cache(true);
}); });
it('Read from Stream and write to multiple Streams', function (done) { it('Read from Stream and write to multiple Streams', (_t, done) => {
let finishEventsExpected = 2; let finishEventsExpected = 2;
// Output stream 1 // Output stream 1
const output1 = fixtures.path('output.multi-stream.1.jpg'); const output1 = fixtures.path('output.multi-stream.1.jpg');
const writable1 = fs.createWriteStream(output1); const writable1 = fs.createWriteStream(output1);
writable1.on('finish', function () { writable1.on('finish', () => {
sharp(output1).toBuffer(function (err, data, info) { sharp(output1).toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
@@ -40,8 +41,8 @@ describe('Clone', function () {
// Output stream 2 // Output stream 2
const output2 = fixtures.path('output.multi-stream.2.jpg'); const output2 = fixtures.path('output.multi-stream.2.jpg');
const writable2 = fs.createWriteStream(output2); const writable2 = fs.createWriteStream(output2);
writable2.on('finish', function () { writable2.on('finish', () => {
sharp(output2).toBuffer(function (err, data, info) { sharp(output2).toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
@@ -64,14 +65,14 @@ describe('Clone', function () {
fs.createReadStream(fixtures.inputJpg).pipe(rotator); fs.createReadStream(fixtures.inputJpg).pipe(rotator);
}); });
it('Stream-based input attaches finish event listener to original', function () { it('Stream-based input attaches finish event listener to original', () => {
const original = sharp(); const original = sharp();
const clone = original.clone(); const clone = original.clone();
assert.strictEqual(1, original.listenerCount('finish')); assert.strictEqual(1, original.listenerCount('finish'));
assert.strictEqual(0, clone.listenerCount('finish')); assert.strictEqual(0, clone.listenerCount('finish'));
}); });
it('Non Stream-based input does not attach finish event listeners', function () { it('Non Stream-based input does not attach finish event listeners', () => {
const original = sharp(fixtures.inputJpg); const original = sharp(fixtures.inputJpg);
const clone = original.clone(); const clone = original.clone();
assert.strictEqual(0, original.listenerCount('finish')); assert.strictEqual(0, original.listenerCount('finish'));

View File

@@ -1,22 +1,23 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Colour space conversion', function () { describe('Colour space conversion', () => {
it('To greyscale', function (done) { it('To greyscale', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.greyscale() .greyscale()
.toFile(fixtures.path('output.greyscale-gamma-0.0.jpg'), done); .toFile(fixtures.path('output.greyscale-gamma-0.0.jpg'), done);
}); });
it('To greyscale with gamma correction', function (done) { it('To greyscale with gamma correction', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.gamma() .gamma()
@@ -24,19 +25,19 @@ describe('Colour space conversion', function () {
.toFile(fixtures.path('output.greyscale-gamma-2.2.jpg'), done); .toFile(fixtures.path('output.greyscale-gamma-2.2.jpg'), done);
}); });
it('Not to greyscale', function (done) { it('Not to greyscale', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.greyscale(false) .greyscale(false)
.toFile(fixtures.path('output.greyscale-not.jpg'), done); .toFile(fixtures.path('output.greyscale-not.jpg'), done);
}); });
it('Greyscale with single channel output', function (done) { it('Greyscale with single channel output', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.greyscale() .greyscale()
.toColourspace('b-w') .toColourspace('b-w')
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(1, info.channels); assert.strictEqual(1, info.channels);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -55,10 +56,10 @@ describe('Colour space conversion', function () {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
}); });
it('From CMYK to sRGB', function (done) { it('From CMYK to sRGB', (_t, done) => {
sharp(fixtures.inputJpgWithCmykProfile) sharp(fixtures.inputJpgWithCmykProfile)
.resize(320) .resize(320)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
@@ -67,13 +68,13 @@ describe('Colour space conversion', function () {
}); });
}); });
it('From CMYK to sRGB with white background, not yellow', function (done) { it('From CMYK to sRGB with white background, not yellow', (_t, done) => {
sharp(fixtures.inputJpgWithCmykProfile) sharp(fixtures.inputJpgWithCmykProfile)
.resize(320, 240, { .resize(320, 240, {
fit: sharp.fit.contain, fit: sharp.fit.contain,
background: 'white' background: 'white'
}) })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -82,10 +83,10 @@ describe('Colour space conversion', function () {
}); });
}); });
it('From profile-less CMYK to sRGB', function (done) { it('From profile-less CMYK to sRGB', (_t, done) => {
sharp(fixtures.inputJpgWithCmykNoProfile) sharp(fixtures.inputJpgWithCmykNoProfile)
.resize(320) .resize(320)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -122,14 +123,14 @@ describe('Colour space conversion', function () {
); );
}); });
it('CMYK profile to CMYK profile with negate', (done) => { it('CMYK profile to CMYK profile with negate', (_t, done) => {
sharp(fixtures.inputTiffFogra) sharp(fixtures.inputTiffFogra)
.resize(320, 240) .resize(320, 240)
.toColourspace('cmyk') .toColourspace('cmyk')
.pipelineColourspace('cmyk') .pipelineColourspace('cmyk')
.withIccProfile(fixtures.path('XCMYK 2017.icc')) .withIccProfile(fixtures.path('XCMYK 2017.icc'))
.negate() .negate()
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', info.format); assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -143,13 +144,13 @@ describe('Colour space conversion', function () {
}); });
}); });
it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', function (done) { it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', (_t, done) => {
sharp(fixtures.inputPngGradients) sharp(fixtures.inputPngGradients)
.pipelineColourspace('rgb16') .pipelineColourspace('rgb16')
.resize(320) .resize(320)
.gamma() .gamma()
.toColourspace('srgb') .toColourspace('srgb')
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.expected('colourspace-gradients-gamma-resize.png'), data, { fixtures.assertSimilar(fixtures.expected('colourspace-gradients-gamma-resize.png'), data, {
@@ -177,15 +178,15 @@ describe('Colour space conversion', function () {
assert.strictEqual(b, 34); assert.strictEqual(b, 34);
}); });
it('Invalid pipelineColourspace input', function () { it('Invalid pipelineColourspace input', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.pipelineColorspace(null); .pipelineColorspace(null);
}, /Expected string for colourspace but received null of type object/); }, /Expected string for colourspace but received null of type object/);
}); });
it('Invalid toColourspace input', function () { it('Invalid toColourspace input', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.toColourspace(null); .toColourspace(null);
}); });

View File

@@ -1,9 +1,10 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
const sharp = require('../../'); const sharp = require('../../');
@@ -322,7 +323,7 @@ describe('composite', () => {
describe('string gravity', () => { describe('string gravity', () => {
Object.keys(sharp.gravity).forEach(gravity => { Object.keys(sharp.gravity).forEach(gravity => {
it(gravity, done => { it(gravity, done => {
const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg'); const expected = fixtures.expected(`overlay-gravity-${gravity}.jpg`);
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(80) .resize(80)
.composite([{ .composite([{
@@ -344,7 +345,7 @@ describe('composite', () => {
describe('tile and gravity', () => { describe('tile and gravity', () => {
Object.keys(sharp.gravity).forEach(gravity => { Object.keys(sharp.gravity).forEach(gravity => {
it(gravity, done => { it(gravity, done => {
const expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg'); const expected = fixtures.expected(`overlay-tile-gravity-${gravity}.jpg`);
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(80) .resize(80)
.composite([{ .composite([{

View File

@@ -1,15 +1,16 @@
// Copyright 2013 Lovell Fuller and others. /*!
// SPDX-License-Identifier: Apache-2.0 Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Convolve', function () { describe('Convolve', () => {
it('specific convolution kernel 1', function (done) { it('specific convolution kernel 1', (_t, done) => {
sharp(fixtures.inputPngStripesV) sharp(fixtures.inputPngStripesV)
.convolve({ .convolve({
width: 3, width: 3,
@@ -22,7 +23,7 @@ describe('Convolve', function () {
10, 20, 10 10, 20, 10
] ]
}) })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -31,7 +32,7 @@ describe('Convolve', function () {
}); });
}); });
it('specific convolution kernel 2', function (done) { it('specific convolution kernel 2', (_t, done) => {
sharp(fixtures.inputPngStripesH) sharp(fixtures.inputPngStripesH)
.convolve({ .convolve({
width: 3, width: 3,
@@ -42,7 +43,7 @@ describe('Convolve', function () {
1, 0, 1 1, 0, 1
] ]
}) })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -51,7 +52,7 @@ describe('Convolve', function () {
}); });
}); });
it('horizontal Sobel operator', function (done) { it('horizontal Sobel operator', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.convolve({ .convolve({
@@ -63,7 +64,7 @@ describe('Convolve', function () {
-1, 0, 1 -1, 0, 1
] ]
}) })
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
@@ -72,14 +73,14 @@ describe('Convolve', function () {
}); });
}); });
describe('invalid kernel specification', function () { describe('invalid kernel specification', () => {
it('missing', function () { it('missing', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).convolve({}); sharp(fixtures.inputJpg).convolve({});
}); });
}); });
it('incorrect data format', function () { it('incorrect data format', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).convolve({ sharp(fixtures.inputJpg).convolve({
width: 3, width: 3,
height: 3, height: 3,
@@ -87,8 +88,8 @@ describe('Convolve', function () {
}); });
}); });
}); });
it('incorrect dimensions', function () { it('incorrect dimensions', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).convolve({ sharp(fixtures.inputJpg).convolve({
width: 3, width: 3,
height: 4, height: 4,

View File

@@ -1,15 +1,14 @@
'use strict'; const { describe, it } = require('node:test');
const assert = require('node:assert');
const assert = require('assert');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('Dilate', function () { describe('Dilate', () => {
it('dilate 1 png', function (done) { it('dilate 1 png', (_t, done) => {
sharp(fixtures.inputPngDotAndLines) sharp(fixtures.inputPngDotAndLines)
.dilate(1) .dilate(1)
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width); assert.strictEqual(100, info.width);
@@ -18,10 +17,10 @@ describe('Dilate', function () {
}); });
}); });
it('dilate 1 png - default width', function (done) { it('dilate 1 png - default width', (_t, done) => {
sharp(fixtures.inputPngDotAndLines) sharp(fixtures.inputPngDotAndLines)
.dilate() .dilate()
.toBuffer(function (err, data, info) { .toBuffer((err, data, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width); assert.strictEqual(100, info.width);
@@ -30,8 +29,8 @@ describe('Dilate', function () {
}); });
}); });
it('invalid dilation width', function () { it('invalid dilation width', () => {
assert.throws(function () { assert.throws(() => {
sharp(fixtures.inputJpg).dilate(-1); sharp(fixtures.inputJpg).dilate(-1);
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More