Compare commits

...

52 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
Lovell Fuller
ee437832e2 Release v0.34.4 2025-09-17 13:57:10 +01:00
Lovell Fuller
529901177b CI/Docs: Deno v2 support 2025-09-17 13:56:49 +01:00
147 changed files with 3820 additions and 3165 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
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
max_line_length = 120

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.
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.
## 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.
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.
## 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`.
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
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).
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. -->
- [ ] 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 Bun

View File

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

View File

@@ -43,30 +43,30 @@ jobs:
runtime: bun
- name: darwin-x64-node-npm
runs-on: macos-13
runs-on: macos-15-intel
runtime: node
package-manager: npm
- name: darwin-x64-node-pnpm
runs-on: macos-13
runs-on: macos-15-intel
runtime: node
package-manager: pnpm
- name: darwin-x64-node-yarn
runs-on: macos-13
runs-on: macos-15-intel
runtime: node
package-manager: yarn
- name: darwin-x64-node-yarn-pnp
runs-on: macos-13
runs-on: macos-15-intel
runtime: node
package-manager: yarn-pnp
- name: darwin-x64-node-yarn-v1
runs-on: macos-13
runs-on: macos-15-intel
runtime: node
package-manager: yarn-v1
- name: darwin-x64-deno
runs-on: macos-13
runs-on: macos-15-intel
runtime: deno
- name: darwin-x64-bun
runs-on: macos-13
runs-on: macos-15-intel
runtime: bun
- name: win32-x64-node-npm
@@ -96,7 +96,7 @@ jobs:
steps:
- name: Install Node.js
if: ${{ matrix.runtime == 'node' }}
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 20
- name: Install pnpm
@@ -185,7 +185,9 @@ jobs:
- name: Run with Deno
if: ${{ matrix.runtime == 'deno' }}
run: deno run --allow-read --allow-ffi release.mjs
run: |
deno install
deno run --allow-env --allow-ffi --allow-read --allow-sys release.mjs
- name: Run with Bun
if: ${{ matrix.runtime == 'bun' }}

2
.gitignore vendored
View File

@@ -1,7 +1,6 @@
src/build
src/node_modules
node_modules
/coverage
npm/*/*
!npm/*/package.json
test/bench/node_modules
@@ -9,7 +8,6 @@ test/fixtures/output*
test/fixtures/vips-properties.xml
test/leak/libvips.supp
.DS_Store
.nyc_output
.vscode/
package-lock.json
.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
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
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
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';
import starlightAutoSidebar from 'starlight-auto-sidebar';
import { version } from '../package.json';

View File

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

View File

@@ -11,8 +11,9 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.35.2",
"astro": "^5.13.5",
"starlight-auto-sidebar": "^0.1.2"
"@astrojs/starlight": "^0.37.1",
"astro": "^5.16.6",
"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
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.
See also [flatten](/api-operation#flatten).
See also [flatten](/api-operation/#flatten).
**Example**

View File

@@ -80,7 +80,7 @@ as defined by [toColourspace](#tocolourspace).
| 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**
```js
@@ -123,7 +123,7 @@ By default output image will be web-friendly sRGB, with additional channels inte
| 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**
```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`.
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/
@@ -64,8 +64,8 @@ and https://www.cairographics.org/operators/
| [images[].raw.height] | <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[].failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | @see [constructor parameters](/api-constructor#parameters) |
| [images[].limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</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/) |
**Example**
```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.
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.
- `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
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
- `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
- `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
- `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
@@ -50,6 +50,7 @@ A `Promise` is returned when `callback` is not provided.
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
- `formatMagick`: String containing format for images loaded via *magick
- `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([options], [flat], [jagged]) ⇒ <code>Sharp</code>
> sharpen([options]) ⇒ <code>Sharp</code>
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.
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**:
@@ -189,15 +189,13 @@ See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.ht
| 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.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.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.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**
```js
@@ -360,8 +358,6 @@ with all white pixel values made fully transparent.
Existing alpha channel values for non-white pixels remain unchanged.
This feature is experimental and the API may change.
**Since**: 0.32.1
**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() ⇒ <code>Sharp</code>
@@ -201,7 +233,7 @@ const dataWithMergedExif = await sharp(inputWithExif)
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
@@ -211,6 +243,14 @@ const outputWithIccProfile = await sharp(inputWithIccProfile)
.keepIccProfile()
.toBuffer();
```
**Example**
```js
const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile)
.pipelineColourspace('cmyk')
.toColourspace('cmyk')
.keepIccProfile()
.toBuffer();
```
## 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() ⇒ <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.
For 16 bits per pixel output, convert to `rgb16` via
[toColourspace](/api-colour#tocolourspace).
[toColourspace](/api-colour/#tocolourspace).
**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.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.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 |
**Example**
@@ -589,7 +651,7 @@ Use these JP2 options for output image.
Requires libvips compiled with support for OpenJPEG.
The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
[installing a custom libvips](/install#custom-libvips).
**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.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.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.pyramid] | <code>boolean</code> | <code>false</code> | write an image pyramid |
| [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.
Prebuilt binaries support a bitdepth of 8 only.
This feature is experimental on the Windows ARM64 platform
and requires a CPU with ARM64v8.4 or later.
When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
**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.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.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**
```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.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.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**
```js
@@ -753,7 +817,7 @@ This feature is experimental, please do not use in production systems.
Requires libvips compiled with support for libjxl.
The prebuilt binaries do not include this - see
[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips).
[installing a custom libvips](/install/#custom-libvips).
**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.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.margin] | <code>number</code> | <code>0</code> | Leave a margin around trimmed content, value is in pixels. |
**Example**
```js
@@ -320,4 +321,13 @@ const output = await sharp(input)
threshold: 42,
})
.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.
:::note
Further [control over performance](/performance) is available.
Further [control over performance](/performance/) is available.
:::

View File

@@ -1,5 +1,5 @@
---
title: v0.34.4 - TBD
title: v0.34.4 - 17th September 2025
slug: changelog/v0.34.4
---

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
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
quickest ImageMagick and GraphicsMagick settings

View File

@@ -20,10 +20,6 @@ npm install sharp
pnpm add sharp
```
When using `pnpm`, you may need to add `sharp` to
[ignoredBuiltDependencies](https://pnpm.io/settings#ignoredbuiltdependencies)
to silence warnings.
```sh frame="none"
yarn add sharp
```
@@ -33,12 +29,13 @@ bun add sharp
```
```sh frame="none"
deno run --allow-ffi ...
deno add --quiet npm:sharp
deno run --allow-env --allow-ffi --allow-read --allow-sys ...
```
## 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
@@ -48,12 +45,13 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* macOS ARM64
* Linux ARM (glibc >= 2.31)
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
* Linux RISC-V 64-bit (glibc >= 2.41)
* Linux ppc64 (glibc >= 2.36)
* Linux s390x (glibc >= 2.36)
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
* Windows x64
* Windows x86
* Windows ARM64 (experimental, CPU with ARMv8.4 required for all features)
* Windows x86 (deprecated, Node.js 20 only)
* Windows ARM64 (CPU with ARMv8.4 required for all features)
This provides support for the
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
@@ -110,12 +108,13 @@ and on macOS when running Node.js under Rosetta.
## 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
* when the `npm install --build-from-source` flag is used.
The logic to detect a globally-installed libvips can be skipped by setting the
The build process will search for a globally-installed libvips.
This detection logic can be skipped by setting the
`SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or
`SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated)
environment variables.
@@ -126,21 +125,12 @@ Building from source requires:
* [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
There is an install-time check for these dependencies.
If `node-addon-api` or `node-gyp` cannot be found, try adding them via:
```sh frame="none"
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
Experimental support is provided for runtime environments that provide
@@ -163,10 +153,8 @@ as well as the additional [building from source](#building-from-source) dependen
```sh frame="none"
pkg install -y pkgconf vips
```
```sh frame="none"
cd /usr/ports/graphics/vips/ && make install clean
npm install sharp
npm explore sharp -- npm run build
```
## Linux memory allocator
@@ -200,6 +188,12 @@ and how to configure it.
Some package managers use symbolic links
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.
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)"
```
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,
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.
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.
```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
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
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.
*
* See also {@link /api-operation#flatten|flatten}.
* See also {@link /api-operation/#flatten flatten}.
*
* @example
* sharp('rgba.png')
@@ -163,7 +163,7 @@ function bandbool (boolOp) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
// Public instance functions
removeAlpha,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const color = require('@img/colour');
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.
* 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
*
@@ -80,7 +80,7 @@ function grayscale (grayscale) {
* .toColourspace('srgb')
* .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}
* @throws {Error} Invalid parameters
*/
@@ -112,7 +112,7 @@ function pipelineColorspace (colorspace) {
* .toColourspace('rgb16')
* .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}
* @throws {Error} Invalid parameters
*/
@@ -141,7 +141,10 @@ function toColorspace (colorspace) {
* @throws {Error} Invalid 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);
return [
colour.red(),
@@ -172,7 +175,7 @@ function _setBackgroundColourOption (key, value) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
// Public
tint,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const is = require('./is');
@@ -56,7 +56,7 @@ const blend = {
* `hard-light`, `soft-light`, `difference`, `exclusion`.
*
* 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/
*
* @since 0.22.0
@@ -123,8 +123,8 @@ const blend = {
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor/ constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor/ constructor parameters}
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -206,7 +206,7 @@ function composite (images) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Sharp.prototype.composite = composite;
Sharp.blend = blend;
};

View File

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

93
lib/index.d.ts vendored
View File

@@ -27,7 +27,7 @@
/// <reference types="node" />
import { Duplex } from 'stream';
import type { Duplex } from 'node:stream';
//#region Constructor functions
@@ -259,7 +259,6 @@ declare namespace sharp {
* Set the pipeline colourspace.
* 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.
* This feature is experimental and has not yet been fully-tested with all operations.
*
* @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ...
* @throws {Error} Invalid parameters
@@ -470,21 +469,6 @@ declare namespace 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.
* @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 }>;
/**
* 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.
* 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
*/
toFormat(
format: keyof FormatEnum | AvailableFormatInfo,
format: keyof FormatEnum | AvailableFormatInfo | "avif",
options?:
| OutputOptions
| JpegOptions
@@ -860,6 +851,7 @@ declare namespace sharp {
| JxlOptions
| GifOptions
| Jp2Options
| RawOptions
| TiffOptions,
): Sharp;
@@ -902,7 +894,7 @@ declare namespace sharp {
* - 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.
*
* 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.
* - 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.
@@ -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')
*/
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.
* Assumes image dimensions contained in the input metadata can be trusted.
@@ -1027,11 +1011,11 @@ declare namespace sharp {
openSlide?: OpenSlideInputOptions | undefined;
/** JPEG 2000 specific input options */
jp2?: Jp2InputOptions | undefined;
/** Deprecated: use tiff.subifd instead */
/** @deprecated Use {@link SharpOptions.tiff} instead */
subifd?: number | undefined;
/** Deprecated: use pdf.background instead */
/** @deprecated Use {@link SharpOptions.pdf} instead */
pdfBackground?: Colour | Color | undefined;
/** Deprecated: use openSlide.level instead */
/** @deprecated Use {@link SharpOptions.openSlide} instead */
level?: number | undefined;
/** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
animated?: boolean | undefined;
@@ -1181,6 +1165,12 @@ declare namespace sharp {
'IFD3'?: ExifDir;
}
type HeifCompression = 'av1' | 'hevc';
type HeifTune = 'iq' | 'ssim' | 'psnr';
type Unit = 'inch' | 'cm';
interface WriteableMetadata {
/** Number of pixels per inch (DPI) */
density?: number | undefined;
@@ -1259,7 +1249,7 @@ declare namespace sharp {
/** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */
tifftagPhotoshop?: Buffer | undefined;
/** 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 */
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 */
@@ -1267,11 +1257,13 @@ declare namespace sharp {
/** Number of Sub Image File Directories in an OME-TIFF image */
subifds?: number | undefined;
/** The unit of resolution (density) */
resolutionUnit?: 'inch' | 'cm' | undefined;
resolutionUnit?: Unit | undefined;
/** String containing format for images loaded via *magick */
formatMagick?: string | undefined;
/** Array of keyword/text pairs representing PNG text blocks, if present. */
comments?: CommentsMetadata[] | undefined;
/** HDR gain map, if present */
gainMap?: GainMapMetadata | undefined;
}
interface LevelMetadata {
@@ -1284,16 +1276,21 @@ declare namespace sharp {
text: string;
}
interface GainMapMetadata {
/** JPEG image */
image: Buffer;
}
interface Stats {
/** Array of channel statistics for each channel in the image. */
channels: ChannelStats[];
/** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */
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;
/** 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;
/** 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 };
}
@@ -1399,11 +1396,13 @@ declare namespace sharp {
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
effort?: number | undefined;
/** 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) */
mixed?: boolean;
mixed?: boolean | undefined;
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
preset?: keyof PresetEnum | undefined;
/** Preserve the colour data in transparent pixels (optional, default false) */
exact?: boolean | undefined;
}
interface AvifOptions extends OutputOptions {
@@ -1417,13 +1416,15 @@ declare namespace sharp {
chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
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 {
/** quality, integer 1-100 (optional, default 50) */
quality?: number | undefined;
/** compression format: av1, hevc (optional, default 'av1') */
compression?: 'av1' | 'hevc' | undefined;
compression?: HeifCompression | undefined;
/** use lossless compression (optional, default false) */
lossless?: boolean | undefined;
/** 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;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
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 {
@@ -1460,6 +1463,8 @@ declare namespace sharp {
quality?: number | undefined;
/** Compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default 'jpeg') */
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') */
predictor?: string | undefined;
/** Write an image pyramid (optional, default false) */
@@ -1479,7 +1484,7 @@ declare namespace sharp {
/** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */
resolutionUnit?: 'inch' | 'cm' | undefined;
resolutionUnit?: Unit | undefined;
}
interface PngOptions extends OutputOptions {
@@ -1601,10 +1606,12 @@ declare namespace sharp {
threshold?: number | undefined;
/** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
lineArt?: boolean | undefined;
/** Leave a margin around trimmed content, value is in pixels. (optional, default 0) */
margin?: number | undefined;
}
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 */
@@ -1906,16 +1913,13 @@ declare namespace sharp {
}
interface FormatEnum {
avif: AvailableFormatInfo;
dcraw: AvailableFormatInfo;
dz: AvailableFormatInfo;
exr: AvailableFormatInfo;
fits: AvailableFormatInfo;
gif: AvailableFormatInfo;
heif: AvailableFormatInfo;
input: AvailableFormatInfo;
jpeg: AvailableFormatInfo;
jpg: AvailableFormatInfo;
jp2: AvailableFormatInfo;
jxl: AvailableFormatInfo;
magick: AvailableFormatInfo;
@@ -1927,8 +1931,7 @@ declare namespace sharp {
raw: AvailableFormatInfo;
svg: AvailableFormatInfo;
tiff: AvailableFormatInfo;
tif: AvailableFormatInfo;
v: AvailableFormatInfo;
vips: AvailableFormatInfo;
webp: AvailableFormatInfo;
}

View File

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

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const is = require('./is');
const sharp = require('./sharp');
@@ -30,7 +30,7 @@ const inputStreamParameters = [
// Format-specific
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
// Deprecated
'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
'openSlideLevel', 'pdfBackground', 'tiffSubifd'
];
/**
@@ -54,7 +54,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = {
autoOrient: false,
failOn: 'warning',
limitInputPixels: Math.pow(0x3FFF, 2),
limitInputPixels: 0x3FFF ** 2,
ignoreIcc: false,
unlimited: false,
sequentialRead: true
@@ -106,14 +106,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}`);
}
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
if (is.defined(inputOptions.failOn)) {
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.bool(inputOptions.limitInputPixels)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
? Math.pow(0x3FFF, 2)
? 0x3FFF ** 2
: 0;
} else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
@@ -513,7 +505,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}
}
} else if (is.defined(inputOptions)) {
throw new Error('Invalid input options ' + inputOptions);
throw new Error(`Invalid input options ${inputOptions}`);
}
return inputDescriptor;
}
@@ -525,10 +517,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* @param {string} encoding - unused
* @param {Function} callback
*/
function _write (chunk, encoding, callback) {
/* istanbul ignore else */
function _write (chunk, _encoding, callback) {
if (Array.isArray(this.options.input.buffer)) {
/* istanbul ignore else */
if (is.buffer(chunk)) {
if (this.options.input.buffer.length === 0) {
this.on('finish', () => {
@@ -572,17 +562,17 @@ function _isStreamInput () {
* such as resize or rotate.
*
* 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.
*
* - `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
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
* - `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
* - `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
* - `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
@@ -609,6 +599,7 @@ function _isStreamInput () {
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* - `formatMagick`: String containing format for images loaded via *magick
* - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
* - `gainMap.image`: HDR gain map, if present, as compressed JPEG image.
*
* @example
* const metadata = await sharp(input).metadata();
@@ -794,7 +785,7 @@ function stats (callback) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
// Private
_inputOptionsFromObject,

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
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.
* 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
* const data = await sharp(input).sharpen().toBuffer();
@@ -259,45 +259,18 @@ function affine (matrix, options) {
* })
* .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.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function sharpen (options, flat, jagged) {
if (!is.defined(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)) {
function sharpen (options) {
if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
this.options.sharpenSigma = options.sigma;
} else {
@@ -339,7 +312,7 @@ function sharpen (options, flat, jagged) {
}
}
} else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
this.options.sharpenSigma = -1;
}
return this;
}
@@ -485,7 +458,7 @@ function erode (width) {
/**
* 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
* await sharp(rgbaInput)
@@ -510,8 +483,6 @@ function flatten (options) {
*
* Existing alpha channel values for non-white pixels remain unchanged.
*
* This feature is experimental and the API may change.
*
* @since 0.32.1
*
* @example
@@ -660,7 +631,7 @@ function normalize (options) {
/**
* 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.
*
@@ -742,9 +713,7 @@ function convolve (kernel) {
}
// Default scale is sum of kernel values
if (!is.integer(kernel.scale)) {
kernel.scale = kernel.kernel.reduce(function (a, b) {
return a + b;
}, 0);
kernel.scale = kernel.kernel.reduce((a, b) => a + b, 0);
}
// Clip scale to a minimum value of 1
if (kernel.scale < 1) {
@@ -989,7 +958,7 @@ function modulate (options) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
autoOrient,
rotate,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const path = require('node:path');
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.
*
* 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.
*
@@ -76,7 +76,7 @@ function toFile (fileOut, callback) {
err = new Error('Missing output file path');
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
err = new Error('Cannot use same file for input and output');
} else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
} else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2.output.file) {
err = errJp2Save();
}
if (err) {
@@ -97,12 +97,12 @@ function toFile (fileOut, callback) {
* Write output to a Buffer.
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
*
* Use {@link #toformat|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.
*
* 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:
* - `err` is an error, if any.
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
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.
*
@@ -256,7 +291,7 @@ function withExifMerge (exif) {
/**
* 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
*
@@ -265,6 +300,13 @@ function withExifMerge (exif) {
* .keepIccProfile()
* .toBuffer();
*
* @example
* const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile)
* .pipelineColourspace('cmyk')
* .toColourspace('cmyk')
* .keepIccProfile()
* .toBuffer();
*
* @returns {Sharp}
*/
function keepIccProfile () {
@@ -312,6 +354,30 @@ function withIccProfile (icc, options) {
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.
*
@@ -565,7 +631,7 @@ function jpeg (options) {
* Set `palette` to `true` for slower, indexed PNG output.
*
* For 16 bits per pixel output, convert to `rgb16` via
* {@link /api-colour#tocolourspace|toColourspace}.
* {@link /api-colour/#tocolourspace toColourspace}.
*
* @example
* // 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 {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.exact=false] - preserve the colour data in transparent pixels
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -735,6 +802,9 @@ function webp (options) {
if (is.defined(options.mixed)) {
this._setBooleanOption('webpMixed', options.mixed);
}
if (is.defined(options.exact)) {
this._setBooleanOption('webpExact', options.exact);
}
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options);
@@ -845,13 +915,12 @@ function gif (options) {
return this._updateFormatOut('gif', options);
}
/* istanbul ignore next */
/**
* Use these JP2 options for output image.
*
* Requires libvips compiled with support for OpenJPEG.
* 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
* // Convert any input to lossless JP2 output
@@ -880,7 +949,8 @@ function gif (options) {
* @throws {Error} Invalid 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();
}
if (is.object(options)) {
@@ -959,7 +1029,7 @@ function trySetAnimationOptions (source, target) {
/**
* 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.
*
* @example
@@ -976,6 +1046,7 @@ function trySetAnimationOptions (source, target) {
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {string} [options.compression='jpeg'] - compression options: 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 {boolean} [options.pyramid=false] - write an image pyramid
* @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);
}
}
// bigtiff
if (is.defined(options.bigtiff)) {
this._setBooleanOption('tiffBigtiff', options.bigtiff);
}
// predictor
if (is.defined(options.predictor)) {
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.
* Prebuilt binaries support a bitdepth of 8 only.
*
* This feature is experimental on the Windows ARM64 platform
* and requires a CPU with ARM64v8.4 or later.
* When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
*
* @example
* 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 {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 {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr'
* @returns {Sharp}
* @throws {Error} Invalid 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 {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 {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -1176,6 +1253,13 @@ function heif (options) {
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 {
throw is.invalidParameterError('options', 'Object', options);
}
@@ -1189,7 +1273,7 @@ function heif (options) {
*
* Requires libvips compiled with support for libjxl.
* The prebuilt binaries do not include this - see
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
* {@link /install/#custom-libvips installing a custom libvips}.
*
* @since 0.31.3
*
@@ -1502,7 +1586,6 @@ function _setBooleanOption (key, val) {
* @private
*/
function _read () {
/* istanbul ignore else */
if (!this.options.streamOut) {
this.options.streamOut = true;
const stack = Error();
@@ -1619,16 +1702,18 @@ function _pipeline (callback, stack) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
// Public
toFile,
toBuffer,
toUint8Array,
keepExif,
withExif,
withExifMerge,
keepIccProfile,
withIccProfile,
withGainMap,
keepXmp,
withXmp,
keepMetadata,

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const is = require('./is');
@@ -540,10 +540,19 @@ function extract (options) {
* })
* .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 {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 {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}
* @throws {Error} Invalid parameters
*/
@@ -564,6 +573,13 @@ function trim (options) {
if (is.defined(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 {
throw is.invalidParameterError('trim', 'object', options);
}
@@ -579,7 +595,7 @@ function trim (options) {
* @module Sharp
* @private
*/
module.exports = function (Sharp) {
module.exports = (Sharp) => {
Object.assign(Sharp.prototype, {
resize,
extend,

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
// Populate the npm package for the current platform with the local build
@@ -44,9 +44,10 @@ cpSync(releaseDir, libDir, {
}
});
// Generate README
const { name, description } = require(`./${platform}/package.json`);
// Generate README and index.cjs
const { version, name, description } = require(`./${platform}/package.json`);
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
copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE'));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.34.4-rc.4",
"version": "0.35.0-rc.0",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"contributors": [
@@ -89,17 +89,19 @@
"Lachlan Newman <lachnewman007@gmail.com>",
"Dennis Beatty <dennis@dcbeatty.com>",
"Ingvar Stepanyan <me@rreverser.com>",
"Don Denton <don@happycollision.com>"
"Don Denton <don@happycollision.com>",
"Dmytro Tiapukhin <cool.gegeg@gmail.com>"
],
"scripts": {
"install": "node install/check.js",
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types",
"test-lint": "semistandard && cpplint",
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"",
"build": "node install/build.js",
"clean": "rm -rf src/build/ test/fixtures/output.*",
"test": "npm run lint && npm run test-unit",
"lint": "npm run lint-cpp && npm run lint-js && npm run lint-types",
"lint-cpp": "cpplint --quiet src/*.h src/*.cc",
"lint-js": "biome lint",
"lint-types": "tsd --files ./test/types/sharp.test-d.ts",
"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-release-notes": "node npm/release-notes.js",
"docs-build": "node docs/build.mjs",
@@ -138,83 +140,62 @@
],
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.0",
"semver": "^7.7.2"
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.4-rc.4",
"@img/sharp-darwin-x64": "0.34.4-rc.4",
"@img/sharp-libvips-darwin-arm64": "1.2.3",
"@img/sharp-libvips-darwin-x64": "1.2.3",
"@img/sharp-libvips-linux-arm": "1.2.3",
"@img/sharp-libvips-linux-arm64": "1.2.3",
"@img/sharp-libvips-linux-ppc64": "1.2.3",
"@img/sharp-libvips-linux-s390x": "1.2.3",
"@img/sharp-libvips-linux-x64": "1.2.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.3",
"@img/sharp-libvips-linuxmusl-x64": "1.2.3",
"@img/sharp-linux-arm": "0.34.4-rc.4",
"@img/sharp-linux-arm64": "0.34.4-rc.4",
"@img/sharp-linux-ppc64": "0.34.4-rc.4",
"@img/sharp-linux-s390x": "0.34.4-rc.4",
"@img/sharp-linux-x64": "0.34.4-rc.4",
"@img/sharp-linuxmusl-arm64": "0.34.4-rc.4",
"@img/sharp-linuxmusl-x64": "0.34.4-rc.4",
"@img/sharp-wasm32": "0.34.4-rc.4",
"@img/sharp-win32-arm64": "0.34.4-rc.4",
"@img/sharp-win32-ia32": "0.34.4-rc.4",
"@img/sharp-win32-x64": "0.34.4-rc.4"
"@img/sharp-darwin-arm64": "0.35.0-rc.0",
"@img/sharp-darwin-x64": "0.35.0-rc.0",
"@img/sharp-libvips-darwin-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-darwin-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-ppc64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-riscv64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-s390x": "1.3.0-rc.2",
"@img/sharp-libvips-linux-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-x64": "1.3.0-rc.2",
"@img/sharp-linux-arm": "0.35.0-rc.0",
"@img/sharp-linux-arm64": "0.35.0-rc.0",
"@img/sharp-linux-ppc64": "0.35.0-rc.0",
"@img/sharp-linux-riscv64": "0.35.0-rc.0",
"@img/sharp-linux-s390x": "0.35.0-rc.0",
"@img/sharp-linux-x64": "0.35.0-rc.0",
"@img/sharp-linuxmusl-arm64": "0.35.0-rc.0",
"@img/sharp-linuxmusl-x64": "0.35.0-rc.0",
"@img/sharp-wasm32": "0.35.0-rc.0",
"@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": {
"@emnapi/runtime": "^1.5.0",
"@img/sharp-libvips-dev": "1.2.3",
"@img/sharp-libvips-dev-wasm32": "1.2.3",
"@img/sharp-libvips-win32-arm64": "1.2.3",
"@img/sharp-libvips-win32-ia32": "1.2.3",
"@img/sharp-libvips-win32-x64": "1.2.3",
"@biomejs/biome": "^2.3.10",
"@cpplint/cli": "^0.1.0",
"@emnapi/runtime": "^1.7.1",
"@img/sharp-libvips-dev": "1.3.0-rc.2",
"@img/sharp-libvips-dev-wasm32": "1.3.0-rc.2",
"@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": "*",
"cc": "^3.0.1",
"emnapi": "^1.5.0",
"exif-reader": "^2.0.2",
"emnapi": "^1.7.1",
"exif-reader": "^2.0.3",
"extract-zip": "^2.0.1",
"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-gyp": "^11.4.2",
"nyc": "^17.1.0",
"semistandard": "^17.0.0",
"node-gyp": "^12.1.0",
"tar-fs": "^3.1.1",
"tsd": "^0.33.0"
},
"license": "Apache-2.0",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"node": ">=20.9.0"
},
"config": {
"libvips": ">=8.17.2"
"libvips": ">=8.18.0"
},
"funding": {
"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': {
'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
'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_yarn_locator': '<!(node -p "require(\'../lib/libvips\').yarnLocator()")',
'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': [
'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS',
@@ -120,7 +121,7 @@
'conditions': [
['use_global_libvips == "true"', {
# 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)'],
'defines': [
'SHARP_USE_GLOBAL_LIBVIPS'
@@ -282,7 +283,7 @@
'target_name': 'copy-dll',
'type': 'none',
'dependencies': [
'sharp-<(platform_and_arch)'
'sharp-<(platform_and_arch)-<(sharp_version)'
],
'conditions': [
['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 <string>
#include <string.h>
#include <vector>
#include <queue>
#include <map>
#include <mutex> // NOLINT(build/c++11)
#include <mutex>
#include <queue>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "./common.h"
using vips::VImage;
@@ -285,6 +289,7 @@ namespace sharp {
case ImageType::JXL: id = "jxl"; break;
case ImageType::RAD: id = "rad"; break;
case ImageType::DCRAW: id = "dcraw"; break;
case ImageType::UHDR: id = "uhdr"; break;
case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
@@ -335,6 +340,9 @@ namespace sharp {
{ "VipsForeignLoadRadBuffer", ImageType::RAD },
{ "VipsForeignLoadDcRawFile", ImageType::DCRAW },
{ "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
{ "VipsForeignLoadUhdr", ImageType::UHDR },
{ "VipsForeignLoadUhdrFile", ImageType::UHDR },
{ "VipsForeignLoadUhdrBuffer", ImageType::UHDR },
{ "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW }
@@ -352,6 +360,9 @@ namespace sharp {
imageType = it->second;
}
}
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType;
}
@@ -371,6 +382,9 @@ namespace sharp {
imageType = ImageType::MISSING;
}
}
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType;
}
@@ -1123,4 +1137,20 @@ namespace sharp {
}
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

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_
#define SRC_COMMON_H_
#include <atomic>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <atomic>
#include <napi.h>
#include <vips/vips8>
@@ -15,9 +18,9 @@
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.17.2+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 18) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION < 0)
#error "libvips version 8.18.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if defined(__has_include)
@@ -30,7 +33,7 @@ using vips::VImage;
namespace sharp {
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
struct InputDescriptor {
std::string name;
std::string file;
bool autoOrient;
@@ -170,6 +173,7 @@ namespace sharp {
JXL,
RAD,
DCRAW,
UHDR,
VIPS,
RAW,
UNKNOWN,
@@ -394,6 +398,16 @@ namespace sharp {
*/
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
#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 */

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 <numeric>
#include <string>
#include <utility>
#include <vector>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "metadata.h"
#include "./common.h"
#include "./metadata.h"
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);
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
vips_image_map(image.get_image(), readPNGComment, &baton->comments);
}
@@ -178,10 +190,6 @@ class MetadataWorker : public Napi::AsyncWorker {
info.Set("isPalette", baton->isPalette);
if (baton->bitsPerSample > 0) {
info.Set("bitsPerSample", baton->bitsPerSample);
if (baton->isPalette) {
// Deprecated, remove with libvips 8.17.0
info.Set("paletteBitDepth", baton->bitsPerSample);
}
}
if (baton->pages > 0) {
info.Set("pages", baton->pages);
@@ -215,10 +223,10 @@ class MetadataWorker : public Napi::AsyncWorker {
if (!baton->levels.empty()) {
int i = 0;
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);
level.Set("width", l.first);
level.Set("height", l.second);
level.Set("width", width);
level.Set("height", height);
levels.Set(i++, level);
}
info.Set("levels", levels);
@@ -272,13 +280,19 @@ class MetadataWorker : public Napi::AsyncWorker {
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
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) {
int i = 0;
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);
comment.Set("keyword", c.first);
comment.Set("text", c.second);
comment.Set("keyword", keyword);
comment.Set("text", text);
comments.Set(i++, comment);
}
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_
#define SRC_METADATA_H_
#include <string>
#include <vector>
#include <napi.h>
#include "./common.h"
@@ -50,6 +53,8 @@ struct MetadataBaton {
size_t xmpLength;
char *tifftagPhotoshop;
size_t tifftagPhotoshopLength;
char *gainMap;
size_t gainMapLength;
MetadataComments comments;
std::string err;
@@ -79,7 +84,9 @@ struct MetadataBaton {
xmp(nullptr),
xmpLength(0),
tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {}
tifftagPhotoshopLength(0),
gainMap(nullptr),
gainMapLength(0) {}
};
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 <functional>
@@ -8,8 +10,8 @@
#include <vector>
#include <vips/vips8>
#include "common.h"
#include "operations.h"
#include "./common.h"
#include "./operations.h"
using vips::VImage;
using vips::VError;
@@ -283,7 +285,7 @@ namespace sharp {
/*
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) {
throw VError("Image to trim must be at least 3x3 pixels");
}
@@ -318,18 +320,36 @@ namespace sharp {
if (widthA > 0 && heightA > 0) {
if (width > 0 && height > 0) {
// Combined bounding box (B)
int const leftB = std::min(left, leftA);
int const topB = std::min(top, topA);
int const widthB = std::max(left + width, leftA + widthA) - leftB;
int const heightB = std::max(top + height, topA + heightA) - topB;
int leftB = std::min(left, leftA);
int topB = std::min(top, topA);
int widthB = std::max(left + width, leftA + widthA) - leftB;
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);
} else {
// 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);
}
}
}
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;

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_
#define SRC_OPERATIONS_H_
@@ -8,6 +10,7 @@
#include <functional>
#include <memory>
#include <tuple>
#include <vector>
#include <vips/vips8>
using vips::VImage;
@@ -79,7 +82,7 @@ namespace sharp {
/*
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)

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 <cmath>
#include <filesystem>
#include <filesystem> // NOLINT(build/c++17)
#include <map>
#include <memory>
#include <numeric>
@@ -17,9 +19,9 @@
#include <vips/vips8>
#include <napi.h>
#include "common.h"
#include "operations.h"
#include "pipeline.h"
#include "./common.h"
#include "./operations.h"
#include "./pipeline.h"
class PipelineWorker : public Napi::AsyncWorker {
public:
@@ -151,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->trimThreshold >= 0.0) {
MultiPageUnsupported(nPages, "Trim");
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->trimOffsetTop = image.yoffset();
}
@@ -294,6 +296,14 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->input->autoOrient) {
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
inputWidth = image.width();
@@ -333,7 +343,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
!baton->input->ignoreIcc
!baton->input->ignoreIcc && !baton->withGainMap
) {
// Convert to sRGB/P3 using embedded profile
try {
@@ -453,12 +463,10 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
// Embed
int left;
int top;
std::tie(left, top) = sharp::CalculateEmbedPosition(
const auto& [left, top] = sharp::CalculateEmbedPosition(
inputWidth, inputHeight, baton->width, baton->height, baton->position);
int width = std::max(inputWidth, baton->width);
int height = std::max(inputHeight, baton->height);
const int width = std::max(inputWidth, baton->width);
const int height = std::max(inputHeight, baton->height);
image = nPages > 1
? sharp::EmbedMultiPage(image,
@@ -477,13 +485,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Crop
if (baton->position < 9) {
// Gravity-based crop
int left;
int top;
std::tie(left, top) = sharp::CalculateCrop(
const auto& [left, top] = sharp::CalculateCrop(
inputWidth, inputHeight, baton->width, baton->height, baton->position);
int width = std::min(inputWidth, baton->width);
int height = std::min(inputHeight, baton->height);
const int width = std::min(inputWidth, baton->width);
const int height = std::min(inputHeight, baton->height);
image = nPages > 1
? sharp::CropMultiPage(image,
@@ -795,20 +800,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
}
// Convert image to sRGB, if not already
// Ensure output colour space
if (sharp::Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT);
}
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()));
// Transform colours from embedded profile to output profile
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
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));
if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {
image = sharp::SetProfile(image, inputProfile);
}
}
@@ -843,8 +842,6 @@ class PipelineWorker : public Napi::AsyncWorker {
} catch(...) {
sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
}
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
image = sharp::SetProfile(image, inputProfile);
}
// Negate the colours in the image
@@ -866,8 +863,8 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!baton->withExifMerge) {
image = sharp::RemoveExif(image);
}
for (const auto& s : baton->withExif) {
image.set(s.first.data(), s.second.data());
for (const auto& [key, value] : baton->withExif) {
image.set(key.c_str(), value.c_str());
}
}
// XMP buffer
@@ -968,6 +965,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@@ -1009,6 +1007,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->tiffQuality)
->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression)
->set("bigtiff", baton->tiffBigtiff)
->set("miniswhite", baton->tiffMiniswhite)
->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
@@ -1034,6 +1033,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless)));
@@ -1178,6 +1178,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1211,6 +1212,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->tiffQuality)
->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression)
->set("bigtiff", baton->tiffBigtiff)
->set("miniswhite", baton->tiffMiniswhite)
->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
@@ -1232,6 +1234,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless));
@@ -1276,7 +1279,12 @@ class PipelineWorker : public Napi::AsyncWorker {
if (what && what[0]) {
(baton->err).append(what);
} 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
@@ -1291,7 +1299,11 @@ class PipelineWorker : public Napi::AsyncWorker {
// Handle warnings
std::string warning = sharp::VipsWarningPop();
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();
}
@@ -1337,12 +1349,21 @@ class PipelineWorker : public Napi::AsyncWorker {
}
if (baton->bufferOutLength > 0) {
// Add buffer size to info
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
// Pass ownership of output data to Buffer instance
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().Call(Receiver().Value(), { env.Null(), data, info });
if (baton->typedArrayOut) {
// ECMAScript ArrayBuffer with Uint8Array view
Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
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 {
// Add file size to info
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
@@ -1435,11 +1456,11 @@ class PipelineWorker : public Napi::AsyncWorker {
std::string
AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
std::string argument;
for (auto const &option : options) {
for (const auto& [key, value] : options) {
if (!argument.empty()) {
argument += ",";
}
argument += option.first + "=" + option.second;
argument += key + "=" + value;
}
return extname + "[" + argument + "]";
}
@@ -1469,6 +1490,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
{"min_size", baton->webpMinSize ? "true" : "false"},
{"mixed", baton->webpMixed ? "true" : "false"},
{"exact", baton->webpExact ? "true" : "false"},
{"effort", std::to_string(baton->webpEffort)}
};
suffix = AssembleSuffixString(".webp", options);
@@ -1615,6 +1637,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1692,6 +1715,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Output
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
@@ -1706,6 +1730,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
}
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
baton->loop = sharp::AttrAsUint32(options, "loop");
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1741,6 +1766,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
@@ -1750,6 +1776,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
@@ -1774,6 +1801,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
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_
#define SRC_PIPELINE_H_
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <vector>
#include <napi.h>
#include <vips/vips8>
@@ -46,6 +48,7 @@ struct PipelineBaton {
size_t bufferOutLength;
int pageHeightOut;
int pagesOut;
bool typedArrayOut;
std::vector<Composite *> composite;
std::vector<sharp::InputDescriptor *> joinChannelIn;
int topOffsetPre;
@@ -99,6 +102,7 @@ struct PipelineBaton {
bool trimLineArt;
int trimOffsetLeft;
int trimOffsetTop;
int trimMargin;
std::vector<double> linearA;
std::vector<double> linearB;
int dilateWidth;
@@ -165,6 +169,7 @@ struct PipelineBaton {
int webpEffort;
bool webpMinSize;
bool webpMixed;
bool webpExact;
int gifBitdepth;
int gifEffort;
double gifDither;
@@ -175,6 +180,7 @@ struct PipelineBaton {
bool gifProgressive;
int tiffQuality;
VipsForeignTiffCompression tiffCompression;
bool tiffBigtiff;
VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid;
int tiffBitdepth;
@@ -191,12 +197,14 @@ struct PipelineBaton {
std::string heifChromaSubsampling;
bool heifLossless;
int heifBitdepth;
std::string heifTune;
double jxlDistance;
int jxlDecodingTier;
int jxlEffort;
bool jxlLossless;
VipsBandFormat rawDepth;
std::string err;
bool errUseWarning;
int keepMetadata;
int withMetadataOrientation;
double withMetadataDensity;
@@ -204,6 +212,7 @@ struct PipelineBaton {
std::unordered_map<std::string, std::string> withExif;
bool withExifMerge;
std::string withXmp;
bool withGainMap;
int timeoutSeconds;
std::vector<double> convKernel;
int convKernelWidth;
@@ -238,6 +247,7 @@ struct PipelineBaton {
bufferOutLength(0),
pageHeightOut(0),
pagesOut(0),
typedArrayOut(false),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
@@ -277,6 +287,7 @@ struct PipelineBaton {
trimLineArt(false),
trimOffsetLeft(0),
trimOffsetTop(0),
trimMargin(0),
linearA{},
linearB{},
dilateWidth(0),
@@ -340,6 +351,7 @@ struct PipelineBaton {
webpEffort(4),
webpMinSize(false),
webpMixed(false),
webpExact(false),
gifBitdepth(8),
gifEffort(7),
gifDither(1.0),
@@ -350,6 +362,7 @@ struct PipelineBaton {
gifProgressive(false),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffBigtiff(false),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffBitdepth(8),
@@ -366,15 +379,18 @@ struct PipelineBaton {
heifChromaSubsampling("4:4:4"),
heifLossless(false),
heifBitdepth(8),
heifTune("ssim"),
jxlDistance(1.0),
jxlDecodingTier(0),
jxlEffort(7),
jxlLossless(false),
rawDepth(VIPS_FORMAT_UCHAR),
errUseWarning(false),
keepMetadata(0),
withMetadataOrientation(-1),
withMetadataDensity(0.0),
withExifMerge(true),
withGainMap(false),
timeoutSeconds(0),
convKernelWidth(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 <vips/vips8>
#include "common.h"
#include "metadata.h"
#include "pipeline.h"
#include "utilities.h"
#include "stats.h"
#include "./common.h"
#include "./metadata.h"
#include "./pipeline.h"
#include "./stats.h"
#include "./utilities.h"
Napi::Object init(Napi::Env env, Napi::Object exports) {
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 <numeric>
#include <string>
#include <vector>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "stats.h"
#include "./common.h"
#include "./stats.h"
class StatsWorker : public Napi::AsyncWorker {
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_
#define SRC_STATS_H_
#include <string>
#include <vector>
#include <napi.h>
#include "./common.h"
@@ -24,7 +27,7 @@ struct ChannelStats {
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
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) {}
};

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 <string>
#include <cstdio>
#include <string>
#include <napi.h>
#include <vips/vips8>
#include <vips/vector.h>
#include "common.h"
#include "operations.h"
#include "utilities.h"
#include "./common.h"
#include "./operations.h"
#include "./utilities.h"
/*
Get and set cache limits
@@ -121,6 +123,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw"
}) {
std::string id = f == "jp2k" ? "jp2" : f;
// Input
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
@@ -152,11 +155,11 @@ Napi::Value format(const Napi::CallbackInfo& info) {
output.Set("stream", hasOutputBuffer);
// Other attributes
Napi::Object container = Napi::Object::New(env);
container.Set("id", f);
container.Set("id", id);
container.Set("input", input);
container.Set("output", output);
// Add to set of formats
format.Set(f, container);
format.Set(id, container);
}
// 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_
#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
RUN mkdir /tmp/sharp
RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git
RUN cd /tmp/sharp && npm install --build-from-source
RUN cd /tmp/sharp && npm install && npm run build
# Install benchmark test
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
'use strict';
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
process.env.UV_THREADPOOL_SIZE = 64;
const assert = require('assert');
const assert = require('node:assert');
const async = require('async');
const sharp = require('../../');
@@ -16,32 +16,29 @@ const height = 480;
sharp.concurrency(1);
const timer = setInterval(function () {
const timer = setInterval(() => {
console.dir(sharp.counters());
}, 100);
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function (parallelism, next) {
const start = new Date().getTime();
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], (parallelism, next) => {
const start = Date.now();
async.times(parallelism,
function (id, callback) {
/* jslint unused: false */
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function (err, buffer) {
(_id, callback) => {
sharp(fixtures.inputJpg).resize(width, height).toBuffer((err, buffer) => {
buffer = null;
callback(err, new Date().getTime() - start);
callback(err, Date.now() - start);
});
},
function (err, ids) {
(err, ids) => {
assert(!err);
assert(ids.length === parallelism);
ids.sort();
const mean = ids.reduce(function (a, b) {
return a + b;
}) / ids.length;
console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms');
const mean = ids.reduce((a, b) => a + b) / ids.length;
console.log(`${parallelism} parallel calls: fastest=${ids[0]}ms slowest=${ids[ids.length - 1]}ms mean=${mean}ms`);
next();
}
);
}, function () {
}, () => {
clearInterval(timer);
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('fs');
const { execSync } = require('child_process');
const fs = require('node:fs');
const { execSync } = require('node:child_process');
const async = require('async');
const Benchmark = require('benchmark');
@@ -12,7 +12,7 @@ const Benchmark = require('benchmark');
const safeRequire = (name) => {
try {
return require(name);
} catch (err) {}
} catch (_err) {}
return null;
};
@@ -45,13 +45,13 @@ console.log(`Detected ${physicalCores} physical cores`);
sharp.concurrency(physicalCores);
async.series({
jpeg: function (callback) {
jpeg: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
const jpegSuite = new Benchmark.Suite('jpeg');
// jimp
jpegSuite.add('jimp-buffer-buffer', {
defer: true,
fn: async function (deferred) {
fn: async (deferred) => {
const image = await Jimp.read(inputJpgBuffer);
await image
.resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC })
@@ -60,7 +60,7 @@ async.series({
}
}).add('jimp-file-file', {
defer: true,
fn: async function (deferred) {
fn: async (deferred) => {
const image = await Jimp.read(fixtures.inputJpg);
await image
.resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC })
@@ -71,14 +71,14 @@ async.series({
// mapnik
mapnik && jpegSuite.add('mapnik-file-file', {
defer: true,
fn: function (deferred) {
mapnik.Image.open(fixtures.inputJpg, function (err, img) {
fn: (deferred) => {
mapnik.Image.open(fixtures.inputJpg, (err, img) => {
if (err) throw err;
img
.resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos
})
.save(outputJpg, 'jpeg:quality=80', function (err) {
.save(outputJpg, 'jpeg:quality=80', (err) => {
if (err) throw err;
deferred.resolve();
});
@@ -86,14 +86,14 @@ async.series({
}
}).add('mapnik-buffer-buffer', {
defer: true,
fn: function (deferred) {
mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) {
fn: (deferred) => {
mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, (err, img) => {
if (err) throw err;
img
.resize(width, height, {
scaling_method: mapnik.imageScaling.lanczos
})
.encode('jpeg:quality=80', function (err) {
.encode('jpeg:quality=80', (err) => {
if (err) throw err;
deferred.resolve();
});
@@ -103,7 +103,7 @@ async.series({
// imagemagick
jpegSuite.add('imagemagick-file-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
imagemagick.resize({
srcPath: fixtures.inputJpg,
dstPath: outputJpg,
@@ -112,7 +112,7 @@ async.series({
height,
format: 'jpg',
filter: 'Lanczos'
}, function (err) {
}, (err) => {
if (err) {
throw err;
} else {
@@ -124,12 +124,12 @@ async.series({
// gm
jpegSuite.add('gm-buffer-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(inputJpgBuffer)
.filter('Lanczos')
.resize(width, height)
.quality(80)
.write(outputJpg, function (err) {
.write(outputJpg, (err) => {
if (err) {
throw err;
} else {
@@ -139,12 +139,12 @@ async.series({
}
}).add('gm-buffer-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(inputJpgBuffer)
.filter('Lanczos')
.resize(width, height)
.quality(80)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -154,12 +154,12 @@ async.series({
}
}).add('gm-file-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(fixtures.inputJpg)
.filter('Lanczos')
.resize(width, height)
.quality(80)
.write(outputJpg, function (err) {
.write(outputJpg, (err) => {
if (err) {
throw err;
} else {
@@ -169,12 +169,12 @@ async.series({
}
}).add('gm-file-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(fixtures.inputJpg)
.filter('Lanczos')
.resize(width, height)
.quality(80)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -186,17 +186,17 @@ async.series({
// tfjs
tfjs && jpegSuite.add('tfjs-node-buffer-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
const decoded = tfjs.node.decodeJpeg(inputJpgBuffer);
const resized = tfjs.image.resizeBilinear(decoded, [height, width]);
tfjs
.node
.encodeJpeg(resized, 'rgb', 80)
.then(function () {
.then(() => {
deferred.resolve();
tfjs.disposeVariables();
})
.catch(function (err) {
.catch((err) => {
throw err;
});
}
@@ -204,10 +204,10 @@ async.series({
// sharp
jpegSuite.add('sharp-buffer-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.toFile(outputJpg, function (err) {
.toFile(outputJpg, (err) => {
if (err) {
throw err;
} else {
@@ -217,10 +217,10 @@ async.series({
}
}).add('sharp-buffer-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} 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', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputJpg)
.resize(width, height)
.toFile(outputJpg, function (err) {
.toFile(outputJpg, (err) => {
if (err) {
throw err;
} else {
@@ -243,10 +256,10 @@ async.series({
}
}).add('sharp-stream-stream', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
const readable = fs.createReadStream(fixtures.inputJpg);
const writable = fs.createWriteStream(outputJpg);
writable.on('finish', function () {
writable.on('finish', () => {
deferred.resolve();
});
const pipeline = sharp()
@@ -255,10 +268,10 @@ async.series({
}
}).add('sharp-file-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputJpg)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -266,36 +279,49 @@ async.series({
}
});
}
}).add('sharp-promise', {
}).add('sharp-file-uint8array', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
fn: (deferred) => {
sharp(fixtures.inputJpg)
.resize(width, height)
.toBuffer()
.then(function () {
.toUint8Array()
.then(() => {
deferred.resolve();
})
.catch(function (err) {
.catch((err) => {
throw err;
});
}
}).on('cycle', function (event) {
console.log('jpeg ' + String(event.target));
}).add('sharp-promise', {
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 () {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Effect of applying operations
operations: function (callback) {
operations: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
const operationsSuite = new Benchmark.Suite('operations');
operationsSuite.add('sharp-sharpen-mild', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.sharpen()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -305,11 +331,11 @@ async.series({
}
}).add('sharp-sharpen-radius', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.sharpen(3, 1, 3)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -319,11 +345,11 @@ async.series({
}
}).add('sharp-blur-mild', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.blur()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -333,11 +359,11 @@ async.series({
}
}).add('sharp-blur-radius', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.blur(3)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -347,11 +373,11 @@ async.series({
}
}).add('sharp-gamma', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.gamma()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -361,11 +387,11 @@ async.series({
}
}).add('sharp-normalise', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.normalise()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -375,11 +401,11 @@ async.series({
}
}).add('sharp-greyscale', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.greyscale()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -389,12 +415,12 @@ async.series({
}
}).add('sharp-greyscale-gamma', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.gamma()
.greyscale()
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -404,11 +430,11 @@ async.series({
}
}).add('sharp-progressive', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.jpeg({ progressive: true })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -418,11 +444,11 @@ async.series({
}
}).add('sharp-without-chroma-subsampling', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -432,11 +458,11 @@ async.series({
}
}).add('sharp-rotate', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.rotate(90)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -446,11 +472,11 @@ async.series({
}
}).add('sharp-without-simd', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp.simd(false);
sharp(inputJpgBuffer)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
sharp.simd(true);
if (err) {
throw err;
@@ -461,10 +487,10 @@ async.series({
}
}).add('sharp-random-access-read', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer, { sequentialRead: false })
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -474,13 +500,13 @@ async.series({
}
}).add('sharp-crop-entropy', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, {
fit: 'cover',
position: sharp.strategy.entropy
})
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -490,13 +516,13 @@ async.series({
}
}).add('sharp-crop-attention', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, {
fit: 'cover',
position: sharp.strategy.attention
})
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -504,21 +530,21 @@ async.series({
}
});
}
}).on('cycle', function (event) {
console.log('operations ' + String(event.target));
}).on('cycle', (event) => {
console.log(`operations ${String(event.target)}`);
}).on('complete', function () {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Comparative speed of kernels
kernels: function (callback) {
kernels: (callback) => {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('kernels')).add('sharp-cubic', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'cubic' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -528,10 +554,10 @@ async.series({
}
}).add('sharp-lanczos2', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos2' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -541,10 +567,10 @@ async.series({
}
}).add('sharp-lanczos3', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos3' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -554,10 +580,10 @@ async.series({
}
}).add('sharp-mks2013', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2013' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -567,10 +593,10 @@ async.series({
}
}).add('sharp-mks2021', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2021' })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -578,21 +604,21 @@ async.series({
}
});
}
}).on('cycle', function (event) {
console.log('kernels ' + String(event.target));
}).on('cycle', (event) => {
console.log(`kernels ${String(event.target)}`);
}).on('complete', function () {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// PNG
png: function (callback) {
png: (callback) => {
const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png');
const minSamples = 64;
// jimp
pngSuite.add('jimp-buffer-buffer', {
defer: true,
fn: async function (deferred) {
fn: async (deferred) => {
const image = await Jimp.read(inputPngBuffer);
await image
.resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC })
@@ -601,7 +627,7 @@ async.series({
}
}).add('jimp-file-file', {
defer: true,
fn: async function (deferred) {
fn: async (deferred) => {
const image = await Jimp.read(fixtures.inputPngAlphaPremultiplicationLarge);
await image
.resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC })
@@ -612,18 +638,18 @@ async.series({
// mapnik
mapnik && pngSuite.add('mapnik-file-file', {
defer: true,
fn: function (deferred) {
mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
fn: (deferred) => {
mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, (err, img) => {
if (err) throw err;
img.premultiply(function (err, img) {
img.premultiply((err, img) => {
if (err) throw err;
img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) {
}, (err, img) => {
if (err) throw err;
img.demultiply(function (err, img) {
img.demultiply((err, img) => {
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;
deferred.resolve();
});
@@ -634,18 +660,18 @@ async.series({
}
}).add('mapnik-buffer-buffer', {
defer: true,
fn: function (deferred) {
mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, function (err, img) {
fn: (deferred) => {
mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, (err, img) => {
if (err) throw err;
img.premultiply(function (err, img) {
img.premultiply((err, img) => {
if (err) throw err;
img.resize(width, heightPng, {
scaling_method: mapnik.imageScaling.lanczos
}, function (err, img) {
}, (err, img) => {
if (err) throw err;
img.demultiply(function (err, img) {
img.demultiply((err, img) => {
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;
deferred.resolve();
});
@@ -658,7 +684,7 @@ async.series({
// imagemagick
pngSuite.add('imagemagick-file-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
imagemagick.resize({
srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: outputPng,
@@ -669,7 +695,7 @@ async.series({
'-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0'
]
}, function (err) {
}, (err) => {
if (err) {
throw err;
} else {
@@ -681,13 +707,13 @@ async.series({
// gm
pngSuite.add('gm-file-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos')
.resize(width, heightPng)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.write(outputPng, function (err) {
.write(outputPng, (err) => {
if (err) {
throw err;
} else {
@@ -697,13 +723,13 @@ async.series({
}
}).add('gm-file-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos')
.resize(width, heightPng)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -716,11 +742,11 @@ async.series({
pngSuite.add('sharp-buffer-file', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
.toFile(outputPng, (err) => {
if (err) {
throw err;
} else {
@@ -731,11 +757,11 @@ async.series({
}).add('sharp-buffer-buffer', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toBuffer(function (err, data) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -746,11 +772,11 @@ async.series({
}).add('sharp-file-file', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toFile(outputPng, function (err) {
.toFile(outputPng, (err) => {
if (err) {
throw err;
} else {
@@ -761,11 +787,11 @@ async.series({
}).add('sharp-file-buffer', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, heightPng)
.png({ compressionLevel: 6 })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -776,11 +802,11 @@ async.series({
}).add('sharp-progressive', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -791,11 +817,11 @@ async.series({
}).add('sharp-adaptiveFiltering', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -806,11 +832,11 @@ async.series({
}).add('sharp-compressionLevel=9', {
defer: true,
minSamples,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputPngBuffer)
.resize(width, heightPng)
.png({ compressionLevel: 9 })
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -819,21 +845,21 @@ async.series({
});
}
});
pngSuite.on('cycle', function (event) {
console.log(' png ' + String(event.target));
pngSuite.on('cycle', (event) => {
console.log(` png ${String(event.target)}`);
}).on('complete', function () {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// WebP
webp: function (callback) {
webp: (callback) => {
const inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputWebPBuffer)
.resize(width, height)
.toFile(outputWebP, function (err) {
.toFile(outputWebP, (err) => {
if (err) {
throw err;
} else {
@@ -843,10 +869,10 @@ async.series({
}
}).add('sharp-buffer-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(inputWebPBuffer)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -856,10 +882,10 @@ async.series({
}
}).add('sharp-file-file', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputWebP)
.resize(width, height)
.toFile(outputWebP, function (err) {
.toFile(outputWebP, (err) => {
if (err) {
throw err;
} else {
@@ -869,10 +895,10 @@ async.series({
}
}).add('sharp-file-buffer', {
defer: true,
fn: function (deferred) {
fn: (deferred) => {
sharp(fixtures.inputWebP)
.resize(width, height)
.toBuffer(function (err) {
.toBuffer((err) => {
if (err) {
throw err;
} else {
@@ -880,19 +906,19 @@ async.series({
}
});
}
}).on('cycle', function (event) {
console.log('webp ' + String(event.target));
}).on('cycle', (event) => {
console.log(`webp ${String(event.target)}`);
}).on('complete', function () {
callback(null, this.filter('fastest').map('name'));
}).run();
}
}, function (err, results) {
}, (err, results) => {
if (err) {
throw err;
}
Object.keys(results).forEach(function (format) {
Object.keys(results).forEach((format) => {
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());

View File

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

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('path');
const path = require('node:path');
const sharp = require('../../');
const maxColourDistance = require('../../lib/sharp')._maxColourDistance;
// Helpers
const getPath = function (filename) {
return path.join(__dirname, filename);
};
const getPath = (filename) => path.join(__dirname, filename);
// 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
@@ -22,7 +20,7 @@ async function fingerprint (image) {
.resize(9, 8, { fit: sharp.fit.fill })
.raw()
.toBuffer()
.then(function (data) {
.then((data) => {
let fingerprint = '';
for (let col = 0; col < 8; col++) {
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
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
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
inputPngGradients: getPath('gradients-rgb8.png'),
inputPngWithSlightGradientBorder: getPath('slight-gradient-border.png'),
inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngCompleteTransparency: getPath('full-transparent.png'),
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
@@ -115,6 +115,7 @@ module.exports = {
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
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
inputTiffGeo: getPath('bonne.geo.tif'), // https://download.osgeo.org/geotiff/samples/intergraph
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
@@ -147,14 +148,12 @@ module.exports = {
path: getPath,
// Path for expected output images
expected: function (filename) {
return getPath(path.join('expected', filename));
},
expected: (filename) => getPath(path.join('expected', filename)),
// Verify similarity of expected vs actual images via fingerprint
// Specify distance threshold using `options={threshold: 42}`, default
// `threshold` is 5;
assertSimilar: async function (expectedImage, actualImage, options, callback) {
assertSimilar: async (expectedImage, actualImage, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = {};
@@ -194,12 +193,12 @@ module.exports = {
}
},
assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) {
assertMaxColourDistance: (actualImagePath, expectedImagePath, acceptedDistance) => {
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') {
throw new TypeError('`expectedImagePath` must be a string; got ' + expectedImagePath);
throw new TypeError(`\`expectedImagePath\` must be a string; got ${expectedImagePath}`);
}
if (typeof acceptedDistance !== 'number') {
// Default threshold
@@ -207,7 +206,7 @@ module.exports = {
}
const distance = maxColourDistance(actualImagePath, expectedImagePath);
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 \
--num-callers=20 \
--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

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 { createReadStream, createWriteStream } from 'fs';
import { createReadStream, createWriteStream } from 'node:fs';
const input: Buffer = Buffer.alloc(0);
const readableStream: NodeJS.ReadableStream = createReadStream(input);
@@ -79,10 +82,13 @@ sharp({
let transformer = sharp()
.resize(300)
.on('info', (info: sharp.OutputInfo) => {
console.log('Image height is ' + info.height);
console.log(`Image height is ${info.height}`);
});
readableStream.pipe(transformer).pipe(writableStream);
sharp().toUint8Array();
sharp().toUint8Array().then(({ data }) => data.byteLength);
console.log(sharp.format);
console.log(sharp.versions);
@@ -228,7 +234,7 @@ sharp(input)
sharp(input)
.resize(100, 100)
.toFormat('jpg')
.toFormat('avif')
.toBuffer({ resolveWithObject: false })
.then((outputBuffer: Buffer) => {
// Resolves with a Buffer object when resolveWithObject is false
@@ -261,9 +267,7 @@ sharp(input)
// Output to tif
sharp(input)
.resize(100, 100)
.toFormat('tif')
.toFormat('tiff')
.toFormat(sharp.format.tif)
.toFormat(sharp.format.tiff)
.toBuffer();
@@ -318,7 +322,7 @@ sharp('input.gif')
// From https://sharp.pixelplumbing.com/api-output#examples-9
// Extract raw RGB pixel data from JPEG input
sharp('input.jpg')
.raw()
.raw({ depth: 'ushort' })
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
console.log(data);
@@ -359,7 +363,7 @@ sharp(input)
.avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
.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 })
.then(({ data, info }) => {
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({ effort: 7 }).toFile('out.jxl');
// Support `minSize` and `mixed` webp options
sharp('input.tiff').webp({ minSize: true, mixed: true }).toFile('out.gif');
// Support webp options
sharp('input.tiff').webp({ minSize: true, mixed: true, exact: true }).toFile('out.webp');
// 'failOn' input param
sharp('input.tiff', { failOn: 'none' });
@@ -595,7 +599,7 @@ const vertexSplitQuadraticBasisSpline: string = sharp.interpolators.vertexSplitQ
// Triming
sharp(input).trim({ background: '#000' }).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
sharp({
@@ -768,3 +772,35 @@ sharp().erode();
sharp().erode(1);
sharp().dilate();
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const fixtures = require('../fixtures');
const sharp = require('../../');
describe('Alpha transparency', function () {
it('Flatten to black', function (done) {
describe('Alpha transparency', () => {
it('Flatten to black', (_t, done) => {
sharp(fixtures.inputPngWithTransparency)
.flatten()
.resize(400, 300)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(400, info.width);
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)
.resize(400, 300)
.flatten({
background: { r: 255, g: 102, b: 0 }
})
.jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(400, info.width);
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)
.resize(400, 300)
.flatten({ background: '#ff6600' })
.jpeg({ chromaSubsampling: '4:4:4' })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(400, info.width);
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');
sharp(fixtures.inputPngWithTransparency16bit)
.flatten({
background: { r: 255, g: 102, b: 0 }
})
.toFile(output, function (err, info) {
.toFile(output, (err, info) => {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
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)
.flatten(false)
.toBuffer(function (err, data, info) {
.toBuffer((err, _data, info) => {
if (err) throw err;
assert.strictEqual('png', info.format);
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)
.flatten({ background: '#ff0000' })
.toBuffer(function (err, data, info) {
.toBuffer((err, _data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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 actual = fixtures.path('output.' + base);
const actual = fixtures.path(`output.${base}`);
const expected = fixtures.expected(base);
return sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536)
.toFile(actual)
.then(function () {
.then(() => {
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 actual = fixtures.path('output.' + base);
const actual = fixtures.path(`output.${base}`);
const expected = fixtures.expected(base);
return sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768)
.toFile(actual)
.then(function () {
.then(() => {
fixtures.assertMaxColourDistance(actual, expected, 102);
});
});
it('Removes alpha from fixtures with transparency, ignores those without', function () {
return Promise.all([
it('Removes alpha from fixtures with transparency, ignores those without', () => Promise.all([
fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency,
fixtures.inputJpg,
fixtures.inputPng,
fixtures.inputWebP
].map(function (input) {
return sharp(input)
].map((input) => sharp(input)
.resize(10)
.removeAlpha()
.toBuffer({ resolveWithObject: true })
.then(function (result) {
.then((result) => {
assert.strictEqual(3, result.info.channels);
});
}));
});
}))));
it('Ensures alpha from fixtures without transparency, ignores those with', function () {
return Promise.all([
it('Ensures alpha from fixtures without transparency, ignores those with', () => Promise.all([
fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency,
fixtures.inputJpg,
fixtures.inputPng,
fixtures.inputWebP
].map(function (input) {
return sharp(input)
].map((input) => sharp(input)
.resize(10)
.ensureAlpha()
.png()
.toBuffer({ resolveWithObject: true })
.then(function (result) {
.then((result) => {
assert.strictEqual(4, result.info.channels);
});
}));
});
}))));
it('Valid ensureAlpha value used for alpha channel', async () => {
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures');
@@ -20,8 +21,8 @@ describe('AVIF', () => {
.resize(32)
.jpeg()
.toBuffer();
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
@@ -49,8 +50,8 @@ describe('AVIF', () => {
.resize(32)
.avif({ effort: 0 })
.toBuffer();
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
@@ -77,8 +78,8 @@ describe('AVIF', () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
@@ -106,8 +107,8 @@ describe('AVIF', () => {
.resize(10)
.avif({ effort: 0 })
.toBuffer();
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 300,
@@ -136,8 +137,8 @@ describe('AVIF', () => {
.sharpen()
.avif({ effort: 0 })
.toBuffer();
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
@@ -174,9 +175,22 @@ describe('AVIF', () => {
)
);
it('Invalid bitdepth value throws error', async () => {
assert.rejects(
it('Invalid bitdepth value throws error', () =>
assert.throws(
() => 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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const fixtures = require('../fixtures');
const sharp = require('../../');
describe('Bandbool per-channel boolean operations', function () {
describe('Bandbool per-channel boolean operations', () => {
[
sharp.bool.and,
sharp.bool.or,
sharp.bool.eor
]
.forEach(function (op) {
it(op + ' operation', function (done) {
.forEach((op) => {
it(`${op} operation`, (_t, done) => {
sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool(op)
.toColourspace('b-w')
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height);
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)
.bandbool('and')
.toBuffer(function (err, data, info) {
.toBuffer((err, _data, info) => {
if (err) throw err;
assert.strictEqual(3, info.channels);
done();
});
});
it('Invalid operation', function () {
assert.throws(function () {
it('Invalid operation', () => {
assert.throws(() => {
sharp().bandbool('fail');
});
});
it('Missing operation', function () {
assert.throws(function () {
it('Missing operation', () => {
assert.throws(() => {
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Blur', function () {
it('specific radius 1', function (done) {
describe('Blur', () => {
it('specific radius 1', (_t, done) => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(1)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.blur(10)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.blur({ sigma: 10 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.blur(0.3)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.blur()
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
@@ -74,17 +75,17 @@ describe('Blur', function () {
});
});
it('invalid radius', function () {
assert.throws(function () {
it('invalid radius', () => {
assert.throws(() => {
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)
.resize(320, 240)
.blur(false)
.toBuffer(function (err, notBlurred, info) {
.toBuffer((err, notBlurred, info) => {
if (err) throw err;
assert.strictEqual(true, notBlurred.length > 0);
assert.strictEqual('jpeg', info.format);
@@ -93,7 +94,7 @@ describe('Blur', function () {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(true)
.toBuffer(function (err, blurred, info) {
.toBuffer((err, blurred, info) => {
if (err) throw err;
assert.strictEqual(true, blurred.length > 0);
assert.strictEqual(true, blurred.length < notBlurred.length);
@@ -105,18 +106,18 @@ describe('Blur', function () {
});
});
it('invalid precision', function () {
assert.throws(function () {
it('invalid precision', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' });
}, /Expected one of: integer, float, approximate for precision but received invalid of type string/);
});
it('invalid minAmplitude', function () {
assert.throws(function () {
it('invalid minAmplitude', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 });
}, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/);
assert.throws(function () {
assert.throws(() => {
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/);
});
@@ -149,8 +150,8 @@ describe('Blur', function () {
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow);
});
it('options.sigma is required if options object is passed', function () {
assert.throws(function () {
it('options.sigma is required if options object is passed', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).blur({ precision: 'invalid' });
}, /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('fs');
const assert = require('assert');
const fs = require('node:fs');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const fixtures = require('../fixtures');
const sharp = require('../../');
describe('Boolean operation between two images', function () {
describe('Boolean operation between two images', () => {
const inputJpgBooleanTestBuffer = fs.readFileSync(fixtures.inputJpgBooleanTest);
[
@@ -17,63 +18,63 @@ describe('Boolean operation between two images', function () {
sharp.bool.or,
sharp.bool.eor
]
.forEach(function (op) {
it(op + ' operation, file', function (done) {
.forEach((op) => {
it(`${op} operation, file`, (_t, done) => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.boolean(fixtures.inputJpgBooleanTest, op)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(320, info.width);
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)
.resize(320, 240)
.boolean(inputJpgBooleanTestBuffer, op)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(320, info.width);
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)
.raw()
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
sharp(fixtures.inputJpg)
.resize(320, 240)
.boolean(data, op, { raw: info })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(320, info.width);
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 () {
assert.throws(function () {
it('Invalid operation', () => {
assert.throws(() => {
sharp().boolean(fixtures.inputJpgBooleanTest, 'fail');
});
});
it('Invalid operation, non-string', function () {
assert.throws(function () {
it('Invalid operation, non-string', () => {
assert.throws(() => {
sharp().boolean(fixtures.inputJpgBooleanTest, null);
});
});
it('Missing input', function () {
assert.throws(function () {
it('Missing input', () => {
assert.throws(() => {
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../lib');
const fixtures = require('../fixtures');
describe('Clahe', function () {
it('width 5 width 5 maxSlope 0', function (done) {
describe('Clahe', () => {
it('width 5 width 5 maxSlope 0', (_t, done) => {
sharp(fixtures.inputJpgClahe)
.clahe({ width: 5, height: 5, maxSlope: 0 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 5, height: 5, maxSlope: 5 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 11, height: 25, maxSlope: 14 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 50, height: 50, maxSlope: 0 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 50, height: 50, maxSlope: 14 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 100, height: 50, maxSlope: 3 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.clahe({ width: 100, height: 100, maxSlope: 0 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
fixtures.assertSimilar(fixtures.expected('clahe-100-100-0.jpg'), data, done);
});
});
it('invalid maxSlope', function () {
assert.throws(function () {
it('invalid maxSlope', () => {
assert.throws(() => {
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 });
});
assert.throws(function () {
assert.throws(() => {
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' });
});
});
it('invalid width', function () {
assert.throws(function () {
it('invalid width', () => {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100.5, height: 100 });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: -5, height: 100 });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: true, height: 100 });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 'string test', height: 100 });
});
});
it('invalid height', function () {
assert.throws(function () {
it('invalid height', () => {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100.5 });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: -5 });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: true });
});
assert.throws(function () {
assert.throws(() => {
sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 'string test' });
});
});
it('invalid options object', function () {
assert.throws(function () {
it('invalid options object', () => {
assert.throws(() => {
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)
.clahe({ width: 100, height: 50 })
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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('fs');
const assert = require('assert');
const fs = require('node:fs');
const { afterEach, beforeEach, describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Clone', function () {
beforeEach(function () {
describe('Clone', () => {
beforeEach(() => {
sharp.cache(false);
});
afterEach(function () {
afterEach(() => {
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;
// Output stream 1
const output1 = fixtures.path('output.multi-stream.1.jpg');
const writable1 = fs.createWriteStream(output1);
writable1.on('finish', function () {
sharp(output1).toBuffer(function (err, data, info) {
writable1.on('finish', () => {
sharp(output1).toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
@@ -40,8 +41,8 @@ describe('Clone', function () {
// Output stream 2
const output2 = fixtures.path('output.multi-stream.2.jpg');
const writable2 = fs.createWriteStream(output2);
writable2.on('finish', function () {
sharp(output2).toBuffer(function (err, data, info) {
writable2.on('finish', () => {
sharp(output2).toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
@@ -64,14 +65,14 @@ describe('Clone', function () {
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 clone = original.clone();
assert.strictEqual(1, original.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 clone = original.clone();
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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Colour space conversion', function () {
it('To greyscale', function (done) {
describe('Colour space conversion', () => {
it('To greyscale', (_t, done) => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.greyscale()
.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)
.resize(320, 240)
.gamma()
@@ -24,19 +25,19 @@ describe('Colour space conversion', function () {
.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)
.resize(320, 240)
.greyscale(false)
.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)
.resize(320, 240)
.greyscale()
.toColourspace('b-w')
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(1, info.channels);
assert.strictEqual(320, info.width);
@@ -55,10 +56,10 @@ describe('Colour space conversion', function () {
assert.strictEqual(format, 'webp');
});
it('From CMYK to sRGB', function (done) {
it('From CMYK to sRGB', (_t, done) => {
sharp(fixtures.inputJpgWithCmykProfile)
.resize(320)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
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)
.resize(320, 240, {
fit: sharp.fit.contain,
background: 'white'
})
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320)
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
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)
.resize(320, 240)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.withIccProfile(fixtures.path('XCMYK 2017.icc'))
.negate()
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
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)
.pipelineColourspace('rgb16')
.resize(320)
.gamma()
.toColourspace('srgb')
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.expected('colourspace-gradients-gamma-resize.png'), data, {
@@ -177,15 +178,15 @@ describe('Colour space conversion', function () {
assert.strictEqual(b, 34);
});
it('Invalid pipelineColourspace input', function () {
assert.throws(function () {
it('Invalid pipelineColourspace input', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.pipelineColorspace(null);
}, /Expected string for colourspace but received null of type object/);
});
it('Invalid toColourspace input', function () {
assert.throws(function () {
it('Invalid toColourspace input', () => {
assert.throws(() => {
sharp(fixtures.inputJpg)
.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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const fixtures = require('../fixtures');
const sharp = require('../../');
@@ -322,7 +323,7 @@ describe('composite', () => {
describe('string gravity', () => {
Object.keys(sharp.gravity).forEach(gravity => {
it(gravity, done => {
const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
const expected = fixtures.expected(`overlay-gravity-${gravity}.jpg`);
sharp(fixtures.inputJpg)
.resize(80)
.composite([{
@@ -344,7 +345,7 @@ describe('composite', () => {
describe('tile and gravity', () => {
Object.keys(sharp.gravity).forEach(gravity => {
it(gravity, done => {
const expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg');
const expected = fixtures.expected(`overlay-tile-gravity-${gravity}.jpg`);
sharp(fixtures.inputJpg)
.resize(80)
.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 assert = require('assert');
const { describe, it } = require('node:test');
const assert = require('node:assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Convolve', function () {
it('specific convolution kernel 1', function (done) {
describe('Convolve', () => {
it('specific convolution kernel 1', (_t, done) => {
sharp(fixtures.inputPngStripesV)
.convolve({
width: 3,
@@ -22,7 +23,7 @@ describe('Convolve', function () {
10, 20, 10
]
})
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('png', info.format);
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)
.convolve({
width: 3,
@@ -42,7 +43,7 @@ describe('Convolve', function () {
1, 0, 1
]
})
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('png', info.format);
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)
.resize(320, 240)
.convolve({
@@ -63,7 +64,7 @@ describe('Convolve', function () {
-1, 0, 1
]
})
.toBuffer(function (err, data, info) {
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
@@ -72,14 +73,14 @@ describe('Convolve', function () {
});
});
describe('invalid kernel specification', function () {
it('missing', function () {
assert.throws(function () {
describe('invalid kernel specification', () => {
it('missing', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).convolve({});
});
});
it('incorrect data format', function () {
assert.throws(function () {
it('incorrect data format', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).convolve({
width: 3,
height: 3,
@@ -87,8 +88,8 @@ describe('Convolve', function () {
});
});
});
it('incorrect dimensions', function () {
assert.throws(function () {
it('incorrect dimensions', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).convolve({
width: 3,
height: 4,

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