Compare commits

...

30 Commits

Author SHA1 Message Date
Lovell Fuller
6d18c6fdc6 Add Media Type to metadata response #4492 2026-02-01 21:20:16 +00:00
Lovell Fuller
2291c0b864 Switch from custom VError to standard runtime_error 2026-01-23 22:42:23 +00:00
Lovell Fuller
ed6b7384d0 Ensure TIFF output bitdepth option is limited to 1, 2 or 4 2026-01-23 21:29:40 +00:00
Lovell Fuller
ef77388a73 Force MSVC to use exception handling
As of 8.18.0, libvips C++ wrapper retrieves error messages at
exception construction time rather than lazily when accessed.

On Windows this led to error messages being referenced rather
than copied, leading to access beyond their lifetime and possible
corruption.
2026-01-22 12:52:48 +00:00
Lovell Fuller
66764b359b Remove unused option parameter added in 8561f0d 2026-01-20 21:10:56 +00:00
Lovell Fuller
8561f0da1d Ensure HEIF primary item is used as default page #4487 2026-01-18 20:24:34 +00:00
Lovell Fuller
0468c1be9f Encoding lossless AVIF is mutually exclusive with iq tuning 2026-01-08 12:43:53 +00:00
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
73 changed files with 1187 additions and 510 deletions

View File

@@ -1,18 +0,0 @@
freebsd_instance:
image_family: freebsd-15-0
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/node22 www/npm
- pkg-config --modversion vips-cpp
install_script:
- npm install
- npm run build
test_script:
- node --test test/unit/io.js

View File

@@ -31,7 +31,7 @@ please open an issue against that package instead.
<!-- Please place an [x] in the relevant box to confirm. --> <!-- Please place an [x] in the relevant box to confirm. -->
- [ ] I am using Node.js with a version that satisfies `^18.17.0 || ^20.3.0 || >=21.0.0` - [ ] I am using Node.js with a version that satisfies `>=20.9.0`
- [ ] I am using Deno - [ ] I am using Deno
- [ ] I am using Bun - [ ] I am using Bun

View File

@@ -9,8 +9,8 @@ jobs:
contents: read contents: read
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: "24" node-version: "24"
- run: npm install --ignore-scripts - run: npm install --ignore-scripts
@@ -31,123 +31,124 @@ jobs:
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: linux-x64 platform: linux-x64
package: true package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20
platform: linux-x64
- os: ubuntu-24.04
container: rockylinux:8
nodejs_arch: x64
nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: linux-x64 platform: linux-x64
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:18-alpine3.17 container: rockylinux:8
nodejs_version_major: 18 nodejs_arch: x64
platform: linuxmusl-x64 nodejs_version: "^24"
package: true nodejs_version_major: 24
platform: linux-x64
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:20-alpine3.18 container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
platform: linuxmusl-x64 platform: linuxmusl-x64
package: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: node:22-alpine3.20 container: node:22-alpine3.20
nodejs_version_major: 22 nodejs_version_major: 22
platform: linuxmusl-x64 platform: linuxmusl-x64
- os: ubuntu-24.04
container: node:24-alpine3.22
nodejs_version_major: 24
platform: linuxmusl-x64
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8 container: arm64v8/rockylinux:8
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: linux-arm64 platform: linux-arm64
package: true package: true
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8 container: arm64v8/rockylinux:8
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: linux-arm64
- os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8
nodejs_arch: arm64
nodejs_version: "^24"
nodejs_version_major: 24
platform: linux-arm64 platform: linux-arm64
- os: macos-15-intel - os: macos-15-intel
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: darwin-x64 platform: darwin-x64
package: true package: true
- os: macos-15-intel - os: macos-15-intel
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: darwin-x64 platform: darwin-x64
- os: macos-15-intel - os: macos-15-intel
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^22.9.0" nodejs_version: "^24"
nodejs_version_major: 22 nodejs_version_major: 24
platform: darwin-x64 platform: darwin-x64
- os: macos-15 - os: macos-15
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: darwin-arm64 platform: darwin-arm64
package: true package: true
- os: macos-15 - os: macos-15
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: darwin-arm64 platform: darwin-arm64
- os: macos-15 - os: macos-15
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^22.9.0" nodejs_version: "^24"
nodejs_version_major: 22 nodejs_version_major: 24
platform: darwin-arm64 platform: darwin-arm64
- os: windows-2022 - os: windows-2022
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10 nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: win32-ia32 platform: win32-ia32
package: true package: true
- os: windows-2022
nodejs_arch: x86
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: win32-ia32
- os: windows-2022
nodejs_arch: x86
nodejs_version: "^22.9.0"
nodejs_version_major: 22
platform: win32-ia32
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
platform: win32-x64 platform: win32-x64
package: true package: true
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^22"
nodejs_version_major: 20 nodejs_version_major: 22
platform: win32-x64 platform: win32-x64
- os: windows-2022 - os: windows-2022
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^22.9.0" nodejs_version: "^24"
nodejs_version_major: 22 nodejs_version_major: 24
platform: win32-x64 platform: win32-x64
- os: windows-11-arm - os: windows-11-arm
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^20.9.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: win32-arm64 platform: win32-arm64
package: true package: true
- os: windows-11-arm - os: windows-11-arm
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^22.9.0" nodejs_version: "^22"
nodejs_version_major: 22 nodejs_version_major: 22
platform: win32-arm64 platform: win32-arm64
- os: windows-11-arm
nodejs_arch: arm64
nodejs_version: "^24"
nodejs_version_major: 24
platform: win32-arm64
steps: steps:
- name: Dependencies (Rocky Linux glibc) - name: Dependencies (Rocky Linux glibc)
if: contains(matrix.container, 'rockylinux') if: contains(matrix.container, 'rockylinux')
@@ -159,22 +160,22 @@ jobs:
run: apk add build-base git python3 font-noto --update-cache run: apk add build-base git python3 font-noto --update-cache
- name: Dependencies (Python 3.11 - macOS, Windows) - name: Dependencies (Python 3.11 - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows') if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-python@v5 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.12"
- name: Dependencies (Node.js) - name: Dependencies (Node.js)
if: "!contains(matrix.platform, 'linuxmusl')" if: "!contains(matrix.platform, 'linuxmusl')"
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version: ${{ matrix.nodejs_version }} node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }} architecture: ${{ matrix.nodejs_arch }}
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- run: npm install - run: npm install
- run: npm run build - run: npm run build
- run: npm run test-unit - run: npm run test-unit
- if: matrix.package - if: matrix.package
run: npm run package-from-local-build run: npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
if: matrix.package if: matrix.package
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
@@ -191,16 +192,18 @@ jobs:
image: ${{ matrix.container }} image: ${{ matrix.container }}
volumes: volumes:
- /opt:/opt:rw,rshared - /opt:/opt:rw,rshared
- /opt:/__e/node20:ro,rshared - /opt:/__e/node24:ro,rshared
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- container: node:18-alpine3.17 - container: node:20-alpine3.20
nodejs_version_major: 18
package: true
- container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
package: true
- container: node:22-alpine3.20
nodejs_version_major: 22
- container: node:24-alpine3.22
nodejs_version_major: 24
steps: steps:
- name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757 - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757
shell: sh shell: sh
@@ -211,13 +214,13 @@ jobs:
ln -s /usr/bin/node /opt/bin/node ln -s /usr/bin/node /opt/bin/node
- name: Dependencies - name: Dependencies
run: apk add build-base git python3 font-noto --update-cache run: apk add build-base git python3 font-noto --update-cache
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- run: npm install - run: npm install
- run: npm run build - run: npm run build
- run: npm run test-unit - run: npm run test-unit
- if: matrix.package - if: matrix.package
run: npm run package-from-local-build run: npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
if: matrix.package if: matrix.package
with: with:
name: linuxmusl-arm64 name: linuxmusl-arm64
@@ -238,20 +241,20 @@ jobs:
base_image: "balenalib/rpi-raspbian:bullseye" base_image: "balenalib/rpi-raspbian:bullseye"
nodejs_arch: armv6l nodejs_arch: armv6l
nodejs_hostname: unofficial-builds.nodejs.org nodejs_hostname: unofficial-builds.nodejs.org
nodejs_version: "18.17.0" nodejs_version: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-s390x - platform: linux-s390x
base_image: "--platform=linux/s390x s390x/debian:bookworm" base_image: "--platform=linux/s390x s390x/debian:bookworm"
nodejs_arch: s390x nodejs_arch: s390x
nodejs_hostname: nodejs.org nodejs_hostname: nodejs.org
nodejs_version: "18.17.0" nodejs_version: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-ppc64 - platform: linux-ppc64
base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm" base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm"
nodejs_arch: ppc64le nodejs_arch: ppc64le
nodejs_hostname: nodejs.org nodejs_hostname: nodejs.org
nodejs_version: "18.17.0" nodejs_version: "20.9.0"
nodejs_version_major: 18 nodejs_version_major: 20
- platform: linux-riscv64 - platform: linux-riscv64
base_image: "--platform=linux/riscv64 riscv64/debian:trixie" base_image: "--platform=linux/riscv64 riscv64/debian:trixie"
compiler_flags: "-march=rv64gc" compiler_flags: "-march=rv64gc"
@@ -260,7 +263,7 @@ jobs:
nodejs_version: "20.19.5" nodejs_version: "20.19.5"
nodejs_version_major: 20 nodejs_version_major: 20
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: uraimo/run-on-arch-action@v3 - uses: uraimo/run-on-arch-action@v3
with: with:
arch: none arch: none
@@ -279,19 +282,39 @@ jobs:
npm run build npm run build
node --test test/unit/io.js node --test test/unit/io.js
npm run package-from-local-build npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
path: npm/${{ matrix.platform }} path: npm/${{ matrix.platform }}
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
build-freebsd:
permissions:
contents: read
needs: lint
name: "build-freebsd"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: vmactions/freebsd-vm@v1
continue-on-error: true
with:
prepare: |
pkg update -f
pkg upgrade -y
pkg install -y devel/git devel/pkgconf graphics/vips www/node22 www/npm
pkg-config --modversion vips-cpp
run: |
npm install
npm run build
node --test test/unit/io.js
build-emscripten: build-emscripten:
permissions: permissions:
contents: read contents: read
needs: lint needs: lint
name: "build-wasm32 [package]" name: "build-wasm32 [package]"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: "emscripten/emsdk:4.0.18" container: "emscripten/emsdk:4.0.21"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Dependencies - name: Dependencies
@@ -311,7 +334,7 @@ jobs:
test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
- run: emmake npm run test-unit - run: emmake npm run test-unit
- run: emmake npm run package-from-local-build - run: emmake npm run package-from-local-build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v6
with: with:
name: wasm32 name: wasm32
path: npm/wasm32 path: npm/wasm32
@@ -329,12 +352,12 @@ jobs:
- build-emscripten - build-emscripten
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v7
with: with:
path: npm path: npm
- name: Create npm workspace tarball - name: Create npm workspace tarball
run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js . run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js .
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version: '24' node-version: '24'
- name: Create release notes - name: Create release notes

View File

@@ -96,7 +96,7 @@ jobs:
steps: steps:
- name: Install Node.js - name: Install Node.js
if: ${{ matrix.runtime == 'node' }} if: ${{ matrix.runtime == 'node' }}
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version: 20 node-version: 20
- name: Install pnpm - name: Install pnpm

2
.gitignore vendored
View File

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

View File

@@ -8,7 +8,7 @@ smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions
It can be used with all JavaScript runtimes It can be used with all JavaScript runtimes
that provide support for Node-API v9, including that provide support for Node-API v9, including
Node.js (^18.17.0 or >= 20.3.0), Deno and Bun. Node.js (>= 20.9.0), Deno and Bun.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings quickest ImageMagick and GraphicsMagick settings

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.3.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",

View File

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

View File

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

View File

@@ -17,7 +17,8 @@ Dimensions in the response will respect the `page` and `pages` properties of the
A `Promise` is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
- `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
- `size`: Total size of image in bytes, for Stream and Buffer input only - `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
@@ -50,6 +51,7 @@ A `Promise` is returned when `callback` is not provided.
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
- `formatMagick`: String containing format for images loaded via *magick - `formatMagick`: String containing format for images loaded via *magick
- `comments`: Array of keyword/text pairs representing PNG text blocks, if present. - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
- `gainMap.image`: HDR gain map, if present, as compressed JPEG image.

View File

@@ -170,7 +170,7 @@ inputStream
## sharpen ## sharpen
> sharpen([options], [flat], [jagged]) ⇒ <code>Sharp</code> > sharpen([options]) ⇒ <code>Sharp</code>
Sharpen the image. Sharpen the image.
@@ -189,15 +189,13 @@ See [libvips sharpen](https://www.libvips.org/API/current/method.Image.sharpen.h
| Param | Type | Default | Description | | Param | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [options] | <code>Object</code> \| <code>number</code> | | if present, is an Object with attributes | | [options] | <code>Object</code> | | if present, is an Object with attributes |
| [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 | | [options.sigma] | <code>number</code> | | the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 |
| [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 | | [options.m1] | <code>number</code> | <code>1.0</code> | the level of sharpening to apply to "flat" areas, between 0 and 1000000 |
| [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 | | [options.m2] | <code>number</code> | <code>2.0</code> | the level of sharpening to apply to "jagged" areas, between 0 and 1000000 |
| [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 | | [options.x1] | <code>number</code> | <code>2.0</code> | threshold between "flat" and "jagged", between 0 and 1000000 |
| [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 | | [options.y2] | <code>number</code> | <code>10.0</code> | maximum amount of brightening, between 0 and 1000000 |
| [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 | | [options.y3] | <code>number</code> | <code>20.0</code> | maximum amount of darkening, between 0 and 1000000 |
| [flat] | <code>number</code> | | (deprecated) see `options.m1`. |
| [jagged] | <code>number</code> | | (deprecated) see `options.m2`. |
**Example** **Example**
```js ```js
@@ -360,8 +358,6 @@ with all white pixel values made fully transparent.
Existing alpha channel values for non-white pixels remain unchanged. Existing alpha channel values for non-white pixels remain unchanged.
This feature is experimental and the API may change.
**Since**: 0.32.1 **Since**: 0.32.1
**Example** **Example**

View File

@@ -117,6 +117,38 @@ await sharp(pixelArray, { raw: { width, height, channels } })
``` ```
## toUint8Array
> toUint8Array() ⇒ <code>Promise.&lt;{data: Uint8Array, info: Object}&gt;</code>
Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
Use [toFormat](#toformat) or one of the format-specific functions such as [jpeg](#jpeg), [png](#png) etc. to set the output format.
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [keepExif](#keepexif) and similar methods for control over this.
Resolves with an `Object` containing:
- `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
- `info` contains properties relating to the output image such as `width` and `height`.
**Since**: v0.35.0
**Example**
```js
const { data, info } = await sharp(input).toUint8Array();
```
**Example**
```js
const { data } = await sharp(input)
.avif()
.toUint8Array();
const base64String = data.toBase64();
```
## keepExif ## keepExif
> keepExif() ⇒ <code>Sharp</code> > keepExif() ⇒ <code>Sharp</code>
@@ -250,6 +282,27 @@ const outputWithP3 = await sharp(input)
``` ```
## withGainMap
> withGainMap() ⇒ <code>Sharp</code>
If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
The input gain map is discarded.
If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
JPEG output options other than `quality` are ignored.
This feature is experimental and the API may change.
**Since**: 0.35.0
**Example**
```js
const outputWithGainMap = await sharp(inputWithGainMap)
.withGainMap()
.toBuffer();
```
## keepXmp ## keepXmp
> keepXmp() ⇒ <code>Sharp</code> > keepXmp() ⇒ <code>Sharp</code>
@@ -510,6 +563,7 @@ Use these WebP options for output image.
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) | | [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
| [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) | | [options.minSize] | <code>boolean</code> | <code>false</code> | prevent use of animation key frames to minimise file size (slow) |
| [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) | | [options.mixed] | <code>boolean</code> | <code>false</code> | allow mixture of lossy and lossless animation frames (slow) |
| [options.exact] | <code>boolean</code> | <code>false</code> | preserve the colour data in transparent pixels |
| [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format | | [options.force] | <code>boolean</code> | <code>true</code> | force WebP output, otherwise attempt to use input format |
**Example** **Example**
@@ -663,7 +717,7 @@ instead of providing `xres` and `yres` in pixels/mm.
| [options.xres] | <code>number</code> | <code>1.0</code> | horizontal resolution in pixels/mm | | [options.xres] | <code>number</code> | <code>1.0</code> | horizontal resolution in pixels/mm |
| [options.yres] | <code>number</code> | <code>1.0</code> | vertical resolution in pixels/mm | | [options.yres] | <code>number</code> | <code>1.0</code> | vertical resolution in pixels/mm |
| [options.resolutionUnit] | <code>string</code> | <code>&quot;&#x27;inch&#x27;&quot;</code> | resolution unit options: inch, cm | | [options.resolutionUnit] | <code>string</code> | <code>&quot;&#x27;inch&#x27;&quot;</code> | resolution unit options: inch, cm |
| [options.bitdepth] | <code>number</code> | <code>8</code> | reduce bitdepth to 1, 2 or 4 bit | | [options.bitdepth] | <code>number</code> | <code>0</code> | reduce bitdepth to 1, 2 or 4 bit |
| [options.miniswhite] | <code>boolean</code> | <code>false</code> | write 1-bit images as miniswhite | | [options.miniswhite] | <code>boolean</code> | <code>false</code> | write 1-bit images as miniswhite |
**Example** **Example**
@@ -687,8 +741,7 @@ Use these AVIF options for output image.
AVIF image sequences are not supported. AVIF image sequences are not supported.
Prebuilt binaries support a bitdepth of 8 only. Prebuilt binaries support a bitdepth of 8 only.
This feature is experimental on the Windows ARM64 platform When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
and requires a CPU with ARM64v8.4 or later.
**Throws**: **Throws**:
@@ -705,6 +758,7 @@ and requires a CPU with ARM64v8.4 or later.
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) | | [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling | | [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit | | [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
| [options.tune] | <code>string</code> | <code>&quot;&#x27;iq&#x27;&quot;</code> | tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr' |
**Example** **Example**
```js ```js
@@ -744,6 +798,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) | | [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling | | [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit | | [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
| [options.tune] | <code>string</code> | <code>&quot;&#x27;ssim&#x27;&quot;</code> | tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq' |
**Example** **Example**
```js ```js

View File

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

View File

@@ -0,0 +1,49 @@
---
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.
* Ensure TIFF output `bitdepth` option is limited to 1, 2 or 4.
* 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)
* Ensure HEIF primary item is used as default page/frame.
[#4487](https://github.com/lovell/sharp/issues/4487)
* Add image Media Type (MIME Type) to metadata response.
[#4492](https://github.com/lovell/sharp/issues/4492)
* Add WebP `exact` option for control over transparent pixel colour values.

View File

@@ -10,7 +10,7 @@ smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions
It can be used with all JavaScript runtimes It can be used with all JavaScript runtimes
that provide support for Node-API v9, including that provide support for Node-API v9, including
Node.js >= 18.17.0, Deno and Bun. Node.js >= 20.9.0, Deno and Bun.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings quickest ImageMagick and GraphicsMagick settings

View File

@@ -20,10 +20,6 @@ npm install sharp
pnpm add sharp pnpm add sharp
``` ```
When using `pnpm`, add `sharp` to
[ignoredBuiltDependencies](https://pnpm.io/settings#ignoredbuiltdependencies)
to silence warnings.
```sh frame="none" ```sh frame="none"
yarn add sharp yarn add sharp
``` ```
@@ -39,7 +35,7 @@ deno run --allow-env --allow-ffi --allow-read --allow-sys ...
## Prerequisites ## Prerequisites
* Node-API v9 compatible runtime e.g. Node.js ^18.17.0 or >=20.3.0. * Node-API v9 compatible runtime e.g. Node.js >= 20.9.0.
## Prebuilt binaries ## Prebuilt binaries
@@ -54,8 +50,8 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* Linux s390x (glibc >= 2.36) * Linux s390x (glibc >= 2.36)
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2) * Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
* Windows x64 * Windows x64
* Windows x86 * Windows x86 (deprecated, Node.js 20 only)
* Windows ARM64 (experimental, CPU with ARMv8.4 required for all features) * Windows ARM64 (CPU with ARMv8.4 required for all features)
This provides support for the This provides support for the
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats. JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
@@ -112,13 +108,13 @@ and on macOS when running Node.js under Rosetta.
## Building from source ## Building from source
This module will be compiled from source when: ```sh frame="none"
npm install sharp
npm explore sharp -- npm run build
```
* a globally-installed libvips is detected, or The build process will search for a globally-installed libvips.
* using `npm explore sharp -- npm run build`, or This detection logic can be skipped by setting the
* using the deprecated `npm run --build-from-source` at `npm install` time.
The logic to detect a globally-installed libvips can be skipped by setting the
`SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or `SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or
`SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated) `SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated)
environment variables. environment variables.
@@ -129,21 +125,12 @@ Building from source requires:
* [node-addon-api](https://www.npmjs.com/package/node-addon-api) version 7+ * [node-addon-api](https://www.npmjs.com/package/node-addon-api) version 7+
* [node-gyp](https://github.com/nodejs/node-gyp#installation) version 9+ and its dependencies * [node-gyp](https://github.com/nodejs/node-gyp#installation) version 9+ and its dependencies
There is an install-time check for these dependencies.
If `node-addon-api` or `node-gyp` cannot be found, try adding them via: If `node-addon-api` or `node-gyp` cannot be found, try adding them via:
```sh frame="none" ```sh frame="none"
npm install --save node-addon-api node-gyp npm install --save node-addon-api node-gyp
``` ```
When using `pnpm`, you may need to add `sharp` to
[onlyBuiltDependencies](https://pnpm.io/settings#onlybuiltdependencies)
to ensure the installation script can be run.
For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
can be used to configure the target environment.
## WebAssembly ## WebAssembly
Experimental support is provided for runtime environments that provide Experimental support is provided for runtime environments that provide
@@ -166,10 +153,8 @@ as well as the additional [building from source](#building-from-source) dependen
```sh frame="none" ```sh frame="none"
pkg install -y pkgconf vips pkg install -y pkgconf vips
``` npm install sharp
npm explore sharp -- npm run build
```sh frame="none"
cd /usr/ports/graphics/vips/ && make install clean
``` ```
## Linux memory allocator ## Linux memory allocator
@@ -203,6 +188,12 @@ and how to configure it.
Some package managers use symbolic links Some package managers use symbolic links
but AWS Lambda does not support these within deployment packages. but AWS Lambda does not support these within deployment packages.
An alternative approach is to use a well-maintained, third-party Lambda Layer:
- [cbschuld/sharp-aws-lambda-layer](https://github.com/cbschuld/sharp-aws-lambda-layer)
- [pH200/sharp-layer](https://github.com/pH200/sharp-layer)
- [zoellner/sharp-heic-lambda-layer](https://github.com/zoellner/sharp-heic-lambda-layer)
To get the best performance select the largest memory available. To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function. A 1536 MB function provides ~12x more CPU time than a 128 MB function.

View File

@@ -10,7 +10,7 @@ const {
spawnRebuild, spawnRebuild,
} = require('../lib/libvips'); } = require('../lib/libvips');
log('Attempting to build from source via node-gyp'); log('Building from source');
log('See https://sharp.pixelplumbing.com/install#building-from-source'); log('See https://sharp.pixelplumbing.com/install#building-from-source');
try { try {
@@ -29,7 +29,7 @@ try {
} }
if (useGlobalLibvips(log)) { if (useGlobalLibvips(log)) {
log(`Detected globally-installed libvips v${globalLibvipsVersion()}`); log(`Found globally-installed libvips v${globalLibvipsVersion()}`);
} }
const status = spawnRebuild(); const status = spawnRebuild();

View File

@@ -1,14 +0,0 @@
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
try {
const { useGlobalLibvips } = require('../lib/libvips');
if (useGlobalLibvips() || process.env.npm_config_build_from_source) {
process.exit(1);
}
} catch (err) {
const summary = err.message.split(/\n/).slice(0, 1);
console.log(`sharp: skipping install check: ${summary}`);
}

View File

@@ -278,6 +278,7 @@ const Sharp = function (input, options) {
trimBackground: [], trimBackground: [],
trimThreshold: -1, trimThreshold: -1,
trimLineArt: false, trimLineArt: false,
trimMargin: 0,
dilateWidth: 0, dilateWidth: 0,
erodeWidth: 0, erodeWidth: 0,
gamma: 0, gamma: 0,
@@ -306,6 +307,7 @@ const Sharp = function (input, options) {
fileOut: '', fileOut: '',
formatOut: 'input', formatOut: 'input',
streamOut: false, streamOut: false,
typedArrayOut: false,
keepMetadata: 0, keepMetadata: 0,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataDensity: 0, withMetadataDensity: 0,
@@ -313,6 +315,7 @@ const Sharp = function (input, options) {
withExif: {}, withExif: {},
withExifMerge: true, withExifMerge: true,
withXmp: '', withXmp: '',
withGainMap: false,
resolveWithObject: false, resolveWithObject: false,
loop: -1, loop: -1,
delay: [], delay: [],
@@ -348,6 +351,7 @@ const Sharp = function (input, options) {
webpEffort: 4, webpEffort: 4,
webpMinSize: false, webpMinSize: false,
webpMixed: false, webpMixed: false,
webpExact: false,
gifBitdepth: 8, gifBitdepth: 8,
gifEffort: 7, gifEffort: 7,
gifDither: 1, gifDither: 1,
@@ -362,7 +366,7 @@ const Sharp = function (input, options) {
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false, tiffPyramid: false,
tiffMiniswhite: false, tiffMiniswhite: false,
tiffBitdepth: 8, tiffBitdepth: 0,
tiffTile: false, tiffTile: false,
tiffTileHeight: 256, tiffTileHeight: 256,
tiffTileWidth: 256, tiffTileWidth: 256,
@@ -375,6 +379,7 @@ const Sharp = function (input, options) {
heifEffort: 4, heifEffort: 4,
heifChromaSubsampling: '4:4:4', heifChromaSubsampling: '4:4:4',
heifBitdepth: 8, heifBitdepth: 8,
heifTune: 'ssim',
jxlDistance: 1, jxlDistance: 1,
jxlDecodingTier: 0, jxlDecodingTier: 0,
jxlEffort: 7, jxlEffort: 7,

78
lib/index.d.ts vendored
View File

@@ -259,7 +259,6 @@ declare namespace sharp {
* Set the pipeline colourspace. * Set the pipeline colourspace.
* The input image will be converted to the provided colourspace at the start of the pipeline. * The input image will be converted to the provided colourspace at the start of the pipeline.
* All operations will use this colourspace before converting to the output colourspace, as defined by toColourspace. * All operations will use this colourspace before converting to the output colourspace, as defined by toColourspace.
* This feature is experimental and has not yet been fully-tested with all operations.
* *
* @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ... * @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ...
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -470,21 +469,6 @@ declare namespace sharp {
*/ */
sharpen(options?: SharpenOptions): Sharp; sharpen(options?: SharpenOptions): Sharp;
/**
* Sharpen the image.
* When used without parameters, performs a fast, mild sharpen of the output image.
* When a sigma is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
* @param sigma the sigma of the Gaussian mask, where sigma = 1 + radius / 2.
* @param flat the level of sharpening to apply to "flat" areas. (optional, default 1.0)
* @param jagged the level of sharpening to apply to "jagged" areas. (optional, default 2.0)
* @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations
*
* @deprecated Use the object parameter `sharpen({sigma, m1, m2, x1, y2, y3})` instead
*/
sharpen(sigma?: number, flat?: number, jagged?: number): Sharp;
/** /**
* Apply median filter. When used without parameters the default window is 3x3. * Apply median filter. When used without parameters the default window is 3x3.
* @param size square mask size: size x size (optional, default 3) * @param size square mask size: size x size (optional, default 3)
@@ -693,6 +677,13 @@ declare namespace sharp {
*/ */
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>; toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
/**
* Write output to a Uint8Array backed by a transferable ArrayBuffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and RAW output are supported.
* By default, the format will match the input image, except SVG input which becomes PNG output.
* @returns A promise that resolves with an object containing the Uint8Array data and an info object containing the output image format, size (bytes), width, height and channels
*/
toUint8Array(): Promise<{ data: Uint8Array; info: OutputInfo }>;
/** /**
* Keep all EXIF metadata from the input image in the output image. * Keep all EXIF metadata from the input image in the output image.
* EXIF metadata is unsupported for TIFF output. * EXIF metadata is unsupported for TIFF output.
@@ -849,7 +840,7 @@ declare namespace sharp {
* @returns A sharp instance that can be used to chain operations * @returns A sharp instance that can be used to chain operations
*/ */
toFormat( toFormat(
format: keyof FormatEnum | AvailableFormatInfo, format: keyof FormatEnum | AvailableFormatInfo | "avif",
options?: options?:
| OutputOptions | OutputOptions
| JpegOptions | JpegOptions
@@ -903,7 +894,7 @@ declare namespace sharp {
* - sharp.gravity: north, northeast, east, southeast, south, southwest, west, northwest, center or centre. * - sharp.gravity: north, northeast, east, southeast, south, southwest, west, northwest, center or centre.
* - sharp.strategy: cover only, dynamically crop using either the entropy or attention strategy. Some of these values are based on the object-position CSS property. * - sharp.strategy: cover only, dynamically crop using either the entropy or attention strategy. Some of these values are based on the object-position CSS property.
* *
* The experimental strategy-based approach resizes so one dimension is at its target length then repeatedly ranks edge regions, * The strategy-based approach resizes so one dimension is at its target length then repeatedly ranks edge regions,
* discarding the edge with the lowest score based on the selected strategy. * discarding the edge with the lowest score based on the selected strategy.
* - entropy: focus on the region with the highest Shannon entropy. * - entropy: focus on the region with the highest Shannon entropy.
* - attention: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. * - attention: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
@@ -992,14 +983,6 @@ declare namespace sharp {
* 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning') * 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
*/ */
failOn?: FailOnOptions | undefined; failOn?: FailOnOptions | undefined;
/**
* By default halt processing and raise an error when loading invalid images.
* Set this flag to false if you'd rather apply a "best effort" to decode images,
* even if the data is corrupt or invalid. (optional, default true)
*
* @deprecated Use `failOn` instead
*/
failOnError?: boolean | undefined;
/** /**
* Do not process input images where the number of pixels (width x height) exceeds this limit. * Do not process input images where the number of pixels (width x height) exceeds this limit.
* Assumes image dimensions contained in the input metadata can be trusted. * Assumes image dimensions contained in the input metadata can be trusted.
@@ -1028,11 +1011,11 @@ declare namespace sharp {
openSlide?: OpenSlideInputOptions | undefined; openSlide?: OpenSlideInputOptions | undefined;
/** JPEG 2000 specific input options */ /** JPEG 2000 specific input options */
jp2?: Jp2InputOptions | undefined; jp2?: Jp2InputOptions | undefined;
/** Deprecated: use tiff.subifd instead */ /** @deprecated Use {@link SharpOptions.tiff} instead */
subifd?: number | undefined; subifd?: number | undefined;
/** Deprecated: use pdf.background instead */ /** @deprecated Use {@link SharpOptions.pdf} instead */
pdfBackground?: Colour | Color | undefined; pdfBackground?: Colour | Color | undefined;
/** Deprecated: use openSlide.level instead */ /** @deprecated Use {@link SharpOptions.openSlide} instead */
level?: number | undefined; level?: number | undefined;
/** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */ /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
animated?: boolean | undefined; animated?: boolean | undefined;
@@ -1184,6 +1167,8 @@ declare namespace sharp {
type HeifCompression = 'av1' | 'hevc'; type HeifCompression = 'av1' | 'hevc';
type HeifTune = 'iq' | 'ssim' | 'psnr';
type Unit = 'inch' | 'cm'; type Unit = 'inch' | 'cm';
interface WriteableMetadata { interface WriteableMetadata {
@@ -1277,6 +1262,8 @@ declare namespace sharp {
formatMagick?: string | undefined; formatMagick?: string | undefined;
/** Array of keyword/text pairs representing PNG text blocks, if present. */ /** Array of keyword/text pairs representing PNG text blocks, if present. */
comments?: CommentsMetadata[] | undefined; comments?: CommentsMetadata[] | undefined;
/** HDR gain map, if present */
gainMap?: GainMapMetadata | undefined;
} }
interface LevelMetadata { interface LevelMetadata {
@@ -1289,16 +1276,21 @@ declare namespace sharp {
text: string; text: string;
} }
interface GainMapMetadata {
/** JPEG image */
image: Buffer;
}
interface Stats { interface Stats {
/** Array of channel statistics for each channel in the image. */ /** Array of channel statistics for each channel in the image. */
channels: ChannelStats[]; channels: ChannelStats[];
/** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */ /** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */
isOpaque: boolean; isOpaque: boolean;
/** Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental) */ /** Histogram-based estimation of greyscale entropy, discarding alpha channel if any */
entropy: number; entropy: number;
/** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental) */ /** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any */
sharpness: number; sharpness: number;
/** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental) */ /** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram */
dominant: { r: number; g: number; b: number }; dominant: { r: number; g: number; b: number };
} }
@@ -1404,11 +1396,13 @@ declare namespace sharp {
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */ /** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
effort?: number | undefined; effort?: number | undefined;
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */ /** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
minSize?: boolean; minSize?: boolean | undefined;
/** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */ /** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
mixed?: boolean; mixed?: boolean | undefined;
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */ /** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
preset?: keyof PresetEnum | undefined; preset?: keyof PresetEnum | undefined;
/** Preserve the colour data in transparent pixels (optional, default false) */
exact?: boolean | undefined;
} }
interface AvifOptions extends OutputOptions { interface AvifOptions extends OutputOptions {
@@ -1422,6 +1416,8 @@ declare namespace sharp {
chromaSubsampling?: string | undefined; chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined; bitdepth?: 8 | 10 | 12 | undefined;
/** Tune output for a quality metric, one of 'iq', 'ssim' or 'psnr' (optional, default 'iq') */
tune?: HeifTune | undefined;
} }
interface HeifOptions extends OutputOptions { interface HeifOptions extends OutputOptions {
@@ -1437,6 +1433,8 @@ declare namespace sharp {
chromaSubsampling?: string | undefined; chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined; bitdepth?: 8 | 10 | 12 | undefined;
/** Tune output for a quality metric, one of 'ssim', 'psnr' or 'iq' (optional, default 'ssim') */
tune?: HeifTune | undefined;
} }
interface GifOptions extends OutputOptions, AnimationOptions { interface GifOptions extends OutputOptions, AnimationOptions {
@@ -1481,8 +1479,8 @@ declare namespace sharp {
xres?: number | undefined; xres?: number | undefined;
/** Vertical resolution in pixels/mm (optional, default 1.0) */ /** Vertical resolution in pixels/mm (optional, default 1.0) */
yres?: number | undefined; yres?: number | undefined;
/** Reduce bitdepth to 1, 2 or 4 bit (optional, default 8) */ /** Reduce bitdepth to 1, 2 or 4 bit (optional) */
bitdepth?: 1 | 2 | 4 | 8 | undefined; bitdepth?: 1 | 2 | 4 | undefined;
/** Write 1-bit images as miniswhite (optional, default false) */ /** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined; miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */ /** Resolution unit options: inch, cm (optional, default 'inch') */
@@ -1608,6 +1606,8 @@ declare namespace sharp {
threshold?: number | undefined; threshold?: number | undefined;
/** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */ /** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
lineArt?: boolean | undefined; lineArt?: boolean | undefined;
/** Leave a margin around trimmed content, value is in pixels. (optional, default 0) */
margin?: number | undefined;
} }
interface RawOptions { interface RawOptions {
@@ -1913,16 +1913,13 @@ declare namespace sharp {
} }
interface FormatEnum { interface FormatEnum {
avif: AvailableFormatInfo;
dcraw: AvailableFormatInfo; dcraw: AvailableFormatInfo;
dz: AvailableFormatInfo; dz: AvailableFormatInfo;
exr: AvailableFormatInfo; exr: AvailableFormatInfo;
fits: AvailableFormatInfo; fits: AvailableFormatInfo;
gif: AvailableFormatInfo; gif: AvailableFormatInfo;
heif: AvailableFormatInfo; heif: AvailableFormatInfo;
input: AvailableFormatInfo;
jpeg: AvailableFormatInfo; jpeg: AvailableFormatInfo;
jpg: AvailableFormatInfo;
jp2: AvailableFormatInfo; jp2: AvailableFormatInfo;
jxl: AvailableFormatInfo; jxl: AvailableFormatInfo;
magick: AvailableFormatInfo; magick: AvailableFormatInfo;
@@ -1934,8 +1931,7 @@ declare namespace sharp {
raw: AvailableFormatInfo; raw: AvailableFormatInfo;
svg: AvailableFormatInfo; svg: AvailableFormatInfo;
tiff: AvailableFormatInfo; tiff: AvailableFormatInfo;
tif: AvailableFormatInfo; vips: AvailableFormatInfo;
v: AvailableFormatInfo;
webp: AvailableFormatInfo; webp: AvailableFormatInfo;
} }

View File

@@ -30,7 +30,7 @@ const inputStreamParameters = [
// Format-specific // Format-specific
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff', 'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
// Deprecated // Deprecated
'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd' 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
]; ];
/** /**
@@ -106,14 +106,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}`); }`);
} }
if (is.object(inputOptions)) { if (is.object(inputOptions)) {
// Deprecated: failOnError
if (is.defined(inputOptions.failOnError)) {
if (is.bool(inputOptions.failOnError)) {
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
} else {
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
}
}
// failOn // failOn
if (is.defined(inputOptions.failOn)) { if (is.defined(inputOptions.failOn)) {
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) { if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
@@ -574,7 +566,8 @@ function _isStreamInput () {
* *
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
* *
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` * - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
* - `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
* - `size`: Total size of image in bytes, for Stream and Buffer input only * - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
@@ -607,6 +600,7 @@ function _isStreamInput () {
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* - `formatMagick`: String containing format for images loaded via *magick * - `formatMagick`: String containing format for images loaded via *magick
* - `comments`: Array of keyword/text pairs representing PNG text blocks, if present. * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
* - `gainMap.image`: HDR gain map, if present, as compressed JPEG image.
* *
* @example * @example
* const metadata = await sharp(input).metadata(); * const metadata = await sharp(input).metadata();

View File

@@ -259,45 +259,18 @@ function affine (matrix, options) {
* }) * })
* .toBuffer(); * .toBuffer();
* *
* @param {Object|number} [options] - if present, is an Object with attributes * @param {Object} [options] - if present, is an Object with attributes
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10 * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000 * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000 * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000 * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000 * @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000 * @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function sharpen (options, flat, jagged) { function sharpen (options) {
if (!is.defined(options)) { if (is.plainObject(options)) {
// No arguments: default to mild sharpen
this.options.sharpenSigma = -1;
} else if (is.bool(options)) {
// Deprecated boolean argument: apply mild sharpen?
this.options.sharpenSigma = options ? -1 : 0;
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
// Deprecated numeric argument: specific sigma
this.options.sharpenSigma = options;
// Deprecated control over flat areas
if (is.defined(flat)) {
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
this.options.sharpenM1 = flat;
} else {
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
}
}
// Deprecated control over jagged areas
if (is.defined(jagged)) {
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
this.options.sharpenM2 = jagged;
} else {
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
}
}
} else if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) { if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
this.options.sharpenSigma = options.sigma; this.options.sharpenSigma = options.sigma;
} else { } else {
@@ -339,7 +312,7 @@ function sharpen (options, flat, jagged) {
} }
} }
} else { } else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options); this.options.sharpenSigma = -1;
} }
return this; return this;
} }
@@ -510,8 +483,6 @@ function flatten (options) {
* *
* Existing alpha channel values for non-white pixels remain unchanged. * Existing alpha channel values for non-white pixels remain unchanged.
* *
* This feature is experimental and the API may change.
*
* @since 0.32.1 * @since 0.32.1
* *
* @example * @example

View File

@@ -76,7 +76,7 @@ function toFile (fileOut, callback) {
err = new Error('Missing output file path'); err = new Error('Missing output file path');
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) { } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
err = new Error('Cannot use same file for input and output'); err = new Error('Cannot use same file for input and output');
} else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) { } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2.output.file) {
err = errJp2Save(); err = errJp2Save();
} }
if (err) { if (err) {
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
return this._pipeline(is.fn(options) ? options : callback, stack); return this._pipeline(is.fn(options) ? options : callback, stack);
} }
/**
* Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
*
* Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
*
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link #keepexif keepExif} and similar methods for control over this.
*
* Resolves with an `Object` containing:
* - `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
* - `info` contains properties relating to the output image such as `width` and `height`.
*
* @since v0.35.0
*
* @example
* const { data, info } = await sharp(input).toUint8Array();
*
* @example
* const { data } = await sharp(input)
* .avif()
* .toUint8Array();
* const base64String = data.toBase64();
*
* @returns {Promise<{ data: Uint8Array, info: Object }>}
*/
function toUint8Array () {
this.options.resolveWithObject = true;
this.options.typedArrayOut = true;
const stack = Error();
return this._pipeline(null, stack);
}
/** /**
* Keep all EXIF metadata from the input image in the output image. * Keep all EXIF metadata from the input image in the output image.
* *
@@ -319,6 +354,30 @@ function withIccProfile (icc, options) {
return this; return this;
} }
/**
* If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
* The input gain map is discarded.
*
* If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
* JPEG output options other than `quality` are ignored.
*
* This feature is experimental and the API may change.
*
* @since 0.35.0
*
* @example
* const outputWithGainMap = await sharp(inputWithGainMap)
* .withGainMap()
* .toBuffer();
*
* @returns {Sharp}
*/
function withGainMap() {
this.options.withGainMap = true;
this.options.colourspace = 'scrgb';
return this;
}
/** /**
* Keep XMP metadata from the input image in the output image. * Keep XMP metadata from the input image in the output image.
* *
@@ -690,6 +749,7 @@ function png (options) {
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds) * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow) * @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow) * @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
* @param {boolean} [options.exact=false] - preserve the colour data in transparent pixels
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@@ -742,6 +802,9 @@ function webp (options) {
if (is.defined(options.mixed)) { if (is.defined(options.mixed)) {
this._setBooleanOption('webpMixed', options.mixed); this._setBooleanOption('webpMixed', options.mixed);
} }
if (is.defined(options.exact)) {
this._setBooleanOption('webpExact', options.exact);
}
} }
trySetAnimationOptions(options, this.options); trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
@@ -887,7 +950,7 @@ function gif (options) {
*/ */
function jp2 (options) { function jp2 (options) {
/* node:coverage ignore next 41 */ /* node:coverage ignore next 41 */
if (!this.constructor.format.jp2k.output.buffer) { if (!this.constructor.format.jp2.output.buffer) {
throw errJp2Save(); throw errJp2Save();
} }
if (is.object(options)) { if (is.object(options)) {
@@ -992,7 +1055,7 @@ function trySetAnimationOptions (source, target) {
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm * @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
* @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit * @param {number} [options.bitdepth=0] - reduce bitdepth to 1, 2 or 4 bit
* @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite * @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@@ -1007,10 +1070,10 @@ function tiff (options) {
} }
} }
if (is.defined(options.bitdepth)) { if (is.defined(options.bitdepth)) {
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) { if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4])) {
this.options.tiffBitdepth = options.bitdepth; this.options.tiffBitdepth = options.bitdepth;
} else { } else {
throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth); throw is.invalidParameterError('bitdepth', '1, 2 or 4', options.bitdepth);
} }
} }
// tiling // tiling
@@ -1092,8 +1155,7 @@ function tiff (options) {
* AVIF image sequences are not supported. * AVIF image sequences are not supported.
* Prebuilt binaries support a bitdepth of 8 only. * Prebuilt binaries support a bitdepth of 8 only.
* *
* This feature is experimental on the Windows ARM64 platform * When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
* and requires a CPU with ARM64v8.4 or later.
* *
* @example * @example
* const data = await sharp(input) * const data = await sharp(input)
@@ -1113,11 +1175,13 @@ function tiff (options) {
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest) * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr'
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function avif (options) { function avif (options) {
return this.heif({ ...options, compression: 'av1' }); const tune = is.object(options) && is.defined(options.tune) ? options.tune : 'iq';
return this.heif({ ...options, compression: 'av1', tune });
} }
/** /**
@@ -1140,6 +1204,7 @@ function avif (options) {
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest) * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @param {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -1188,6 +1253,17 @@ function heif (options) {
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth); throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
} }
} }
if (is.defined(options.tune)) {
if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
if (this.options.heifLossless && options.tune === 'iq') {
this.options.heifTune = 'ssim';
} else {
this.options.heifTune = options.tune;
}
} else {
throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
}
}
} else { } else {
throw is.invalidParameterError('options', 'Object', options); throw is.invalidParameterError('options', 'Object', options);
} }
@@ -1635,11 +1711,13 @@ module.exports = (Sharp) => {
// Public // Public
toFile, toFile,
toBuffer, toBuffer,
toUint8Array,
keepExif, keepExif,
withExif, withExif,
withExifMerge, withExifMerge,
keepIccProfile, keepIccProfile,
withIccProfile, withIccProfile,
withGainMap,
keepXmp, keepXmp,
withXmp, withXmp,
keepMetadata, keepMetadata,

View File

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

View File

@@ -7,12 +7,13 @@
const { familySync, versionSync } = require('detect-libc'); const { familySync, versionSync } = require('detect-libc');
const { version } = require('../package.json');
const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips'); const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
const runtimePlatform = runtimePlatformArch(); const runtimePlatform = runtimePlatformArch();
const paths = [ const paths = [
`../src/build/Release/sharp-${runtimePlatform}.node`, `../src/build/Release/sharp-${runtimePlatform}-${version}.node`,
'../src/build/Release/sharp-wasm32.node', `../src/build/Release/sharp-wasm32-${version}.node`,
`@img/sharp-${runtimePlatform}/sharp.node`, `@img/sharp-${runtimePlatform}/sharp.node`,
'@img/sharp-wasm32/sharp.node' '@img/sharp-wasm32/sharp.node'
]; ];
@@ -72,6 +73,7 @@ if (sharp) {
} else { } else {
help.push( help.push(
`- Manually install libvips >= ${minimumLibvipsVersion}`, `- Manually install libvips >= ${minimumLibvipsVersion}`,
' See https://sharp.pixelplumbing.com/install#building-from-source',
'- Add experimental WebAssembly-based dependencies:', '- Add experimental WebAssembly-based dependencies:',
' npm install --cpu=wasm32 sharp', ' npm install --cpu=wasm32 sharp',
' npm install @img/sharp-wasm32' ' npm install @img/sharp-wasm32'

View File

@@ -24,7 +24,7 @@ const format = sharp.format();
format.heif.output.alias = ['avif', 'heic']; format.heif.output.alias = ['avif', 'heic'];
format.jpeg.output.alias = ['jpe', 'jpg']; format.jpeg.output.alias = ['jpe', 'jpg'];
format.tiff.output.alias = ['tif']; format.tiff.output.alias = ['tif'];
format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx']; format.jp2.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
/** /**
* An Object containing the available interpolators and their proper values * An Object containing the available interpolators and their proper values

View File

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

View File

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

View File

@@ -44,9 +44,10 @@ cpSync(releaseDir, libDir, {
} }
}); });
// Generate README // Generate README and index.cjs
const { name, description } = require(`./${platform}/package.json`); const { version, name, description } = require(`./${platform}/package.json`);
writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`); writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
writeFileSync(join(destDir, 'index.cjs'), `module.exports = require('./lib/sharp-${platform}-${version}.node');`);
// Copy Apache-2.0 LICENSE // Copy Apache-2.0 LICENSE
copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE')); copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE'));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp", "name": "@img/sharp",
"version": "0.34.5", "version": "0.35.0-rc.0",
"private": "true", "private": "true",
"workspaces": [ "workspaces": [
"darwin-arm64", "darwin-arm64",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.34.5", "version": "0.35.0-rc.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
"contributors": [ "contributors": [
@@ -89,12 +89,12 @@
"Lachlan Newman <lachnewman007@gmail.com>", "Lachlan Newman <lachnewman007@gmail.com>",
"Dennis Beatty <dennis@dcbeatty.com>", "Dennis Beatty <dennis@dcbeatty.com>",
"Ingvar Stepanyan <me@rreverser.com>", "Ingvar Stepanyan <me@rreverser.com>",
"Don Denton <don@happycollision.com>" "Don Denton <don@happycollision.com>",
"Dmytro Tiapukhin <cool.gegeg@gmail.com>"
], ],
"scripts": { "scripts": {
"build": "node install/build.js", "build": "node install/build.js",
"install": "node install/check.js || npm run build", "clean": "rm -rf src/build/ test/fixtures/output.*",
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "npm run lint && npm run test-unit", "test": "npm run lint && npm run test-unit",
"lint": "npm run lint-cpp && npm run lint-js && npm run lint-types", "lint": "npm run lint-cpp && npm run lint-js && npm run lint-types",
"lint-cpp": "cpplint --quiet src/*.h src/*.cc", "lint-cpp": "cpplint --quiet src/*.h src/*.cc",
@@ -144,57 +144,56 @@
"semver": "^7.7.3" "semver": "^7.7.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-arm64": "0.35.0-rc.0",
"@img/sharp-darwin-x64": "0.34.5", "@img/sharp-darwin-x64": "0.35.0-rc.0",
"@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm": "1.3.0-rc.2",
"@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.3.0-rc.2",
"@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.3.0-rc.2",
"@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linux-x64": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.0-rc.2",
"@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm": "0.35.0-rc.0",
"@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-arm64": "0.35.0-rc.0",
"@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-ppc64": "0.35.0-rc.0",
"@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-riscv64": "0.35.0-rc.0",
"@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-s390x": "0.35.0-rc.0",
"@img/sharp-linux-x64": "0.34.5", "@img/sharp-linux-x64": "0.35.0-rc.0",
"@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.35.0-rc.0",
"@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.35.0-rc.0",
"@img/sharp-wasm32": "0.34.5", "@img/sharp-wasm32": "0.35.0-rc.0",
"@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-arm64": "0.35.0-rc.0",
"@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-ia32": "0.35.0-rc.0",
"@img/sharp-win32-x64": "0.34.5" "@img/sharp-win32-x64": "0.35.0-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.4", "@biomejs/biome": "^2.3.10",
"@cpplint/cli": "^0.1.0", "@cpplint/cli": "^0.1.0",
"@emnapi/runtime": "^1.7.0", "@emnapi/runtime": "^1.7.1",
"@img/sharp-libvips-dev": "1.2.4", "@img/sharp-libvips-dev": "1.3.0-rc.2",
"@img/sharp-libvips-dev-wasm32": "1.2.4", "@img/sharp-libvips-dev-wasm32": "1.3.0-rc.2",
"@img/sharp-libvips-win32-arm64": "1.2.4", "@img/sharp-libvips-win32-arm64": "1.3.0-rc.2",
"@img/sharp-libvips-win32-ia32": "1.2.4", "@img/sharp-libvips-win32-ia32": "1.3.0-rc.2",
"@img/sharp-libvips-win32-x64": "1.2.4", "@img/sharp-libvips-win32-x64": "1.3.0-rc.2",
"@types/node": "*", "@types/node": "*",
"emnapi": "^1.7.0", "emnapi": "^1.7.1",
"exif-reader": "^2.0.2", "exif-reader": "^2.0.3",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"icc": "^3.0.0", "icc": "^3.0.0",
"jsdoc-to-markdown": "^9.1.3",
"node-addon-api": "^8.5.0", "node-addon-api": "^8.5.0",
"node-gyp": "^11.5.0", "node-gyp": "^12.1.0",
"tar-fs": "^3.1.1", "tar-fs": "^3.1.1",
"tsd": "^0.33.0" "tsd": "^0.33.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"config": { "config": {
"libvips": ">=8.17.3" "libvips": ">=8.18.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"

View File

@@ -5,6 +5,7 @@
'variables': { 'variables': {
'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")', 'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")', 'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")',
'sharp_version': '<!(node -p "require(\'../package.json\').version")',
'sharp_libvips_version': '<!(node -p "require(\'../package.json\').optionalDependencies[\'@img/sharp-libvips-<(platform_and_arch)\']")', 'sharp_libvips_version': '<!(node -p "require(\'../package.json\').optionalDependencies[\'@img/sharp-libvips-<(platform_and_arch)\']")',
'sharp_libvips_yarn_locator': '<!(node -p "require(\'../lib/libvips\').yarnLocator()")', 'sharp_libvips_yarn_locator': '<!(node -p "require(\'../lib/libvips\').yarnLocator()")',
'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")', 'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")',
@@ -20,6 +21,7 @@
'defines': [ 'defines': [
'_VIPS_PUBLIC=__declspec(dllexport)', '_VIPS_PUBLIC=__declspec(dllexport)',
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_HAS_EXCEPTIONS=1',
'G_DISABLE_ASSERT', 'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS', 'G_DISABLE_CAST_CHECKS',
'G_DISABLE_CHECKS' 'G_DISABLE_CHECKS'
@@ -81,7 +83,7 @@
}] }]
] ]
}, { }, {
'target_name': 'sharp-<(platform_and_arch)', 'target_name': 'sharp-<(platform_and_arch)-<(sharp_version)',
'defines': [ 'defines': [
'G_DISABLE_ASSERT', 'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS', 'G_DISABLE_CAST_CHECKS',
@@ -120,7 +122,7 @@
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
# Use pkg-config for include and lib # Use pkg-config for include and lib
'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'], 'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s/-I//g)'],
'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'], 'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'],
'defines': [ 'defines': [
'SHARP_USE_GLOBAL_LIBVIPS' 'SHARP_USE_GLOBAL_LIBVIPS'
@@ -147,7 +149,8 @@
['OS == "win"', { ['OS == "win"', {
'defines': [ 'defines': [
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64',
'_HAS_EXCEPTIONS=1'
], ],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [
@@ -282,7 +285,7 @@
'target_name': 'copy-dll', 'target_name': 'copy-dll',
'type': 'none', 'type': 'none',
'dependencies': [ 'dependencies': [
'sharp-<(platform_and_arch)' 'sharp-<(platform_and_arch)-<(sharp_version)'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {

View File

@@ -289,6 +289,7 @@ namespace sharp {
case ImageType::JXL: id = "jxl"; break; case ImageType::JXL: id = "jxl"; break;
case ImageType::RAD: id = "rad"; break; case ImageType::RAD: id = "rad"; break;
case ImageType::DCRAW: id = "dcraw"; break; case ImageType::DCRAW: id = "dcraw"; break;
case ImageType::UHDR: id = "uhdr"; break;
case ImageType::VIPS: id = "vips"; break; case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
@@ -339,6 +340,9 @@ namespace sharp {
{ "VipsForeignLoadRadBuffer", ImageType::RAD }, { "VipsForeignLoadRadBuffer", ImageType::RAD },
{ "VipsForeignLoadDcRawFile", ImageType::DCRAW }, { "VipsForeignLoadDcRawFile", ImageType::DCRAW },
{ "VipsForeignLoadDcRawBuffer", ImageType::DCRAW }, { "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
{ "VipsForeignLoadUhdr", ImageType::UHDR },
{ "VipsForeignLoadUhdrFile", ImageType::UHDR },
{ "VipsForeignLoadUhdrBuffer", ImageType::UHDR },
{ "VipsForeignLoadVips", ImageType::VIPS }, { "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS }, { "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW } { "VipsForeignLoadRaw", ImageType::RAW }
@@ -356,6 +360,9 @@ namespace sharp {
imageType = it->second; imageType = it->second;
} }
} }
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType; return imageType;
} }
@@ -375,6 +382,9 @@ namespace sharp {
imageType = ImageType::MISSING; imageType = ImageType::MISSING;
} }
} }
if (imageType == ImageType::UHDR) {
imageType = ImageType::JPEG;
}
return imageType; return imageType;
} }
@@ -416,7 +426,7 @@ namespace sharp {
} }
if (ImageTypeSupportsPage(imageType)) { if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", std::max(0, descriptor->page));
} }
switch (imageType) { switch (imageType) {
case ImageType::SVG: case ImageType::SVG:
@@ -446,6 +456,22 @@ namespace sharp {
return option; return option;
} }
/*
Should HEIF image be re-opened using the primary item?
*/
static bool HeifPrimaryPageReopen(VImage image, InputDescriptor *descriptor) {
if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT && image.get_typeof("heif-primary") == G_TYPE_INT) {
if (image.get_int(VIPS_META_N_PAGES) > 1 && descriptor->pages == 1 && descriptor->page == -1) {
int const pagePrimary = image.get_int("heif-primary");
if (pagePrimary != 0) {
descriptor->page = pagePrimary;
return true;
}
}
}
return false;
}
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/ */
@@ -480,12 +506,15 @@ namespace sharp {
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} else if (imageType == ImageType::HEIF && HeifPrimaryPageReopen(image, descriptor)) {
option = GetOptionsForImageType(imageType, descriptor);
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
} }
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what()); throw std::runtime_error(std::string("Input buffer has corrupt header: ") + err.what());
} }
} else { } else {
throw vips::VError("Input buffer contains unsupported image format"); throw std::runtime_error("Input buffer contains unsupported image format");
} }
} }
} else { } else {
@@ -556,10 +585,10 @@ namespace sharp {
imageType = DetermineImageType(descriptor->file.data()); imageType = DetermineImageType(descriptor->file.data());
if (imageType == ImageType::MISSING) { if (imageType == ImageType::MISSING) {
if (descriptor->file.find("<svg") != std::string::npos) { if (descriptor->file.find("<svg") != std::string::npos) {
throw vips::VError("Input file is missing, did you mean " throw std::runtime_error("Input file is missing, did you mean "
"sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?"); "sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
} }
throw vips::VError("Input file is missing: " + descriptor->file); throw std::runtime_error("Input file is missing: " + descriptor->file);
} }
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
@@ -567,12 +596,15 @@ namespace sharp {
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} else if (imageType == ImageType::HEIF && HeifPrimaryPageReopen(image, descriptor)) {
option = GetOptionsForImageType(imageType, descriptor);
image = VImage::new_from_file(descriptor->file.data(), option);
} }
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what()); throw std::runtime_error(std::string("Input file has corrupt header: ") + err.what());
} }
} else { } else {
throw vips::VError("Input file contains unsupported image format"); throw std::runtime_error("Input file contains unsupported image format");
} }
} }
} }
@@ -580,7 +612,7 @@ namespace sharp {
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
if (descriptor->limitInputPixels > 0 && if (descriptor->limitInputPixels > 0 &&
static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) { static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
throw vips::VError("Input image exceeds pixel limit"); throw std::runtime_error("Input image exceeds pixel limit");
} }
return std::make_tuple(image, imageType); return std::make_tuple(image, imageType);
} }
@@ -756,19 +788,19 @@ namespace sharp {
: image.height(); : image.height();
if (imageType == ImageType::JPEG) { if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || height > 65535) { if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format"); throw std::runtime_error("Processed image is too large for the JPEG format");
} }
} else if (imageType == ImageType::WEBP) { } else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || height > 16383) { if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format"); throw std::runtime_error("Processed image is too large for the WebP format");
} }
} else if (imageType == ImageType::GIF) { } else if (imageType == ImageType::GIF) {
if (image.width() > 65535 || height > 65535) { if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format"); throw std::runtime_error("Processed image is too large for the GIF format");
} }
} else if (imageType == ImageType::HEIF) { } else if (imageType == ImageType::HEIF) {
if (image.width() > 16384 || height > 16384) { if (image.width() > 16384 || height > 16384) {
throw vips::VError("Processed image is too large for the HEIF format"); throw std::runtime_error("Processed image is too large for the HEIF format");
} }
} }
} }
@@ -1127,4 +1159,20 @@ namespace sharp {
} }
return image; return image;
} }
/*
Does this image have a gain map?
*/
bool HasGainMap(VImage image) {
return image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB;
}
/*
Removes gain map, if any.
*/
VImage RemoveGainMap(VImage image) {
VImage copy = image.copy();
copy.remove("gainmap-data");
return copy;
}
} // namespace sharp } // namespace sharp

View File

@@ -18,9 +18,9 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8) || \ #if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 18) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 3) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION < 0)
#error "libvips version 8.17.3+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.18.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if defined(__has_include) #if defined(__has_include)
@@ -105,7 +105,7 @@ namespace sharp {
rawPremultiplied(false), rawPremultiplied(false),
rawPageHeight(0), rawPageHeight(0),
pages(1), pages(1),
page(0), page(-1),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
@@ -173,6 +173,7 @@ namespace sharp {
JXL, JXL,
RAD, RAD,
DCRAW, DCRAW,
UHDR,
VIPS, VIPS,
RAW, RAW,
UNKNOWN, UNKNOWN,
@@ -397,6 +398,16 @@ namespace sharp {
*/ */
VImage StaySequential(VImage image, bool condition = true); VImage StaySequential(VImage image, bool condition = true);
/*
Does this image have a gain map?
*/
bool HasGainMap(VImage image);
/*
Removes gain map, if any.
*/
VImage RemoveGainMap(VImage image);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

View File

@@ -31,7 +31,7 @@ class MetadataWorker : public Napi::AsyncWorker {
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
if (imageType != sharp::ImageType::UNKNOWN) { if (imageType != sharp::ImageType::UNKNOWN) {
@@ -141,8 +141,60 @@ class MetadataWorker : public Napi::AsyncWorker {
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength); memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength; baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
} }
// Gain Map
if (image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB) {
size_t gainMapLength;
void const *gainMap = image.get_blob("gainmap-data", &gainMapLength);
baton->gainMap = static_cast<char *>(g_malloc(gainMapLength));
memcpy(baton->gainMap, gainMap, gainMapLength);
baton->gainMapLength = gainMapLength;
}
// PNG comments // PNG comments
vips_image_map(image.get_image(), readPNGComment, &baton->comments); vips_image_map(image.get_image(), readPNGComment, &baton->comments);
// Media type
std::string mediaType;
switch (imageType) {
case sharp::ImageType::JPEG:
case sharp::ImageType::PNG:
case sharp::ImageType::WEBP:
case sharp::ImageType::JP2:
case sharp::ImageType::TIFF:
case sharp::ImageType::GIF:
case sharp::ImageType::FITS:
case sharp::ImageType::JXL:
baton->mediaType = "image/" + baton->format;
break;
case sharp::ImageType::SVG:
baton->mediaType = "image/svg+xml";
break;
case sharp::ImageType::HEIF:
if (baton->compression == "av1") {
baton->mediaType = "image/avif";
} else if (baton->compression == "hevc") {
baton->mediaType = "image/heic";
}
break;
case sharp::ImageType::PDF:
baton->mediaType = "application/pdf";
break;
case sharp::ImageType::OPENSLIDE:
baton->mediaType = "image/tiff";
break;
case sharp::ImageType::PPM:
baton->mediaType = "image/x-portable-pixmap";
break;
case sharp::ImageType::EXR:
baton->mediaType = "image/x-exr";
break;
case sharp::ImageType::RAD:
baton->mediaType = "image/vnd.radiance";
break;
case sharp::ImageType::UHDR:
baton->mediaType = "image/jpeg";
break;
default:
break;
}
} }
// Clean up // Clean up
@@ -164,6 +216,9 @@ class MetadataWorker : public Napi::AsyncWorker {
if (baton->err.empty()) { if (baton->err.empty()) {
Napi::Object info = Napi::Object::New(env); Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format); info.Set("format", baton->format);
if (!baton->mediaType.empty()) {
info.Set("mediaType", baton->mediaType);
}
if (baton->input->bufferLength > 0) { if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength); info.Set("size", baton->input->bufferLength);
} }
@@ -182,10 +237,6 @@ class MetadataWorker : public Napi::AsyncWorker {
info.Set("isPalette", baton->isPalette); info.Set("isPalette", baton->isPalette);
if (baton->bitsPerSample > 0) { if (baton->bitsPerSample > 0) {
info.Set("bitsPerSample", baton->bitsPerSample); info.Set("bitsPerSample", baton->bitsPerSample);
if (baton->isPalette) {
// Deprecated, remove with libvips 8.17.0
info.Set("paletteBitDepth", baton->bitsPerSample);
}
} }
if (baton->pages > 0) { if (baton->pages > 0) {
info.Set("pages", baton->pages); info.Set("pages", baton->pages);
@@ -276,6 +327,12 @@ class MetadataWorker : public Napi::AsyncWorker {
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop, Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
baton->tifftagPhotoshopLength, sharp::FreeCallback)); baton->tifftagPhotoshopLength, sharp::FreeCallback));
} }
if (baton->gainMapLength > 0) {
Napi::Object gainMap = Napi::Object::New(env);
info.Set("gainMap", gainMap);
gainMap.Set("image",
Napi::Buffer<char>::NewOrCopy(env, baton->gainMap, baton->gainMapLength, sharp::FreeCallback));
}
if (baton->comments.size() > 0) { if (baton->comments.size() > 0) {
int i = 0; int i = 0;
Napi::Array comments = Napi::Array::New(env, baton->comments.size()); Napi::Array comments = Napi::Array::New(env, baton->comments.size());

View File

@@ -19,6 +19,7 @@ struct MetadataBaton {
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
// Output // Output
std::string format; std::string format;
std::string mediaType;
int width; int width;
int height; int height;
std::string space; std::string space;
@@ -53,6 +54,8 @@ struct MetadataBaton {
size_t xmpLength; size_t xmpLength;
char *tifftagPhotoshop; char *tifftagPhotoshop;
size_t tifftagPhotoshopLength; size_t tifftagPhotoshopLength;
char *gainMap;
size_t gainMapLength;
MetadataComments comments; MetadataComments comments;
std::string err; std::string err;
@@ -82,7 +85,9 @@ struct MetadataBaton {
xmp(nullptr), xmp(nullptr),
xmpLength(0), xmpLength(0),
tifftagPhotoshop(nullptr), tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0),
gainMap(nullptr),
gainMapLength(0) {}
}; };
Napi::Value metadata(const Napi::CallbackInfo& info); Napi::Value metadata(const Napi::CallbackInfo& info);

View File

@@ -14,7 +14,6 @@
#include "./operations.h" #include "./operations.h"
using vips::VImage; using vips::VImage;
using vips::VError;
namespace sharp { namespace sharp {
/* /*
@@ -285,9 +284,9 @@ namespace sharp {
/* /*
Trim an image Trim an image
*/ */
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) { VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin) {
if (image.width() < 3 && image.height() < 3) { if (image.width() < 3 && image.height() < 3) {
throw VError("Image to trim must be at least 3x3 pixels"); throw std::runtime_error("Image to trim must be at least 3x3 pixels");
} }
if (background.size() == 0) { if (background.size() == 0) {
// Top-left pixel provides the default background colour if none is given // Top-left pixel provides the default background colour if none is given
@@ -320,18 +319,36 @@ namespace sharp {
if (widthA > 0 && heightA > 0) { if (widthA > 0 && heightA > 0) {
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
// Combined bounding box (B) // Combined bounding box (B)
int const leftB = std::min(left, leftA); int leftB = std::min(left, leftA);
int const topB = std::min(top, topA); int topB = std::min(top, topA);
int const widthB = std::max(left + width, leftA + widthA) - leftB; int widthB = std::max(left + width, leftA + widthA) - leftB;
int const heightB = std::max(top + height, topA + heightA) - topB; int heightB = std::max(top + height, topA + heightA) - topB;
if (margin > 0) {
leftB = std::max(0, leftB - margin);
topB = std::max(0, topB - margin);
widthB = std::min(image.width() - leftB, widthB + 2 * margin);
heightB = std::min(image.height() - topB, heightB + 2 * margin);
}
return image.extract_area(leftB, topB, widthB, heightB); return image.extract_area(leftB, topB, widthB, heightB);
} else { } else {
// Use alpha only // Use alpha only
if (margin > 0) {
leftA = std::max(0, leftA - margin);
topA = std::max(0, topA - margin);
widthA = std::min(image.width() - leftA, widthA + 2 * margin);
heightA = std::min(image.height() - topA, heightA + 2 * margin);
}
return image.extract_area(leftA, topA, widthA, heightA); return image.extract_area(leftA, topA, widthA, heightA);
} }
} }
} }
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
if (margin > 0) {
left = std::max(0, left - margin);
top = std::max(0, top - margin);
width = std::min(image.width() - left, width + 2 * margin);
height = std::min(image.height() - top, height + 2 * margin);
}
return image.extract_area(left, top, width, height); return image.extract_area(left, top, width, height);
} }
return image; return image;
@@ -343,7 +360,7 @@ namespace sharp {
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) { VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
size_t const bands = static_cast<size_t>(image.bands()); size_t const bands = static_cast<size_t>(image.bands());
if (a.size() > bands) { if (a.size() > bands) {
throw VError("Band expansion using linear is unsupported"); throw std::runtime_error("Band expansion using linear is unsupported");
} }
bool const uchar = !Is16Bit(image.interpretation()); bool const uchar = !Is16Bit(image.interpretation());
if (image.has_alpha() && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) { if (image.has_alpha() && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {

View File

@@ -82,7 +82,7 @@ namespace sharp {
/* /*
Trim an image Trim an image
*/ */
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt); VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin);
/* /*
* Linear adjustment (a * in + b) * Linear adjustment (a * in + b)

View File

@@ -84,7 +84,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (nPages == -1) { if (nPages == -1) {
// Resolve the number of pages if we need to render until the end of the document // Resolve the number of pages if we need to render until the end of the document
nPages = image.get_typeof(VIPS_META_N_PAGES) != 0 nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
? image.get_int(VIPS_META_N_PAGES) - baton->input->page ? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)
: 1; : 1;
} }
@@ -153,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->trimThreshold >= 0.0) { if (baton->trimThreshold >= 0.0) {
MultiPageUnsupported(nPages, "Trim"); MultiPageUnsupported(nPages, "Trim");
image = sharp::StaySequential(image); image = sharp::StaySequential(image);
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt); image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
baton->trimOffsetLeft = image.xoffset(); baton->trimOffsetLeft = image.xoffset();
baton->trimOffsetTop = image.yoffset(); baton->trimOffsetTop = image.yoffset();
} }
@@ -274,7 +274,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
sharp::SetDensity(image, baton->input->density); sharp::SetDensity(image, baton->input->density);
if (image.width() > 32767 || image.height() > 32767) { if (image.width() > 32767 || image.height() > 32767) {
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled"); throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
} }
} else if (inputImageType == sharp::ImageType::PDF) { } else if (inputImageType == sharp::ImageType::PDF) {
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
@@ -290,12 +290,20 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
} else { } else {
if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) { if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit"); throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
} }
} }
if (baton->input->autoOrient) { if (baton->input->autoOrient) {
image = sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
if (sharp::HasGainMap(image)) {
if (baton->withGainMap) {
image = image.uhdr2scRGB();
}
image = sharp::RemoveGainMap(image);
} else {
baton->withGainMap = false;
}
// Any pre-shrinking may already have been done // Any pre-shrinking may already have been done
inputWidth = image.width(); inputWidth = image.width();
@@ -335,7 +343,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image.interpretation() != VIPS_INTERPRETATION_LABS && image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16 && image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
!baton->input->ignoreIcc !baton->input->ignoreIcc && !baton->withGainMap
) { ) {
// Convert to sRGB/P3 using embedded profile // Convert to sRGB/P3 using embedded profile
try { try {
@@ -667,7 +675,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Verify within current dimensions // Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) { if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
throw vips::VError("Image to composite must have same dimensions or smaller"); throw std::runtime_error("Image to composite must have same dimensions or smaller");
} }
// Check if overlay is tiled // Check if overlay is tiled
if (composite->tile) { if (composite->tile) {
@@ -957,6 +965,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort) ->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize) ->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed) ->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality))); ->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@@ -1024,6 +1033,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort) ->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth) ->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless))); ->set("lossless", baton->heifLossless)));
@@ -1076,20 +1086,19 @@ class PipelineWorker : public Napi::AsyncWorker {
// Get raw image data // Get raw image data
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength)); baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
if (baton->bufferOut == nullptr) { if (baton->bufferOut == nullptr) {
(baton->err).append("Could not allocate enough memory for raw output"); throw std::runtime_error("Could not allocate enough memory for raw output");
return Error();
} }
baton->formatOut = "raw"; baton->formatOut = "raw";
} else { } else {
// Unsupported output format // Unsupported output format
(baton->err).append("Unsupported output format "); auto unsupported = std::string("Unsupported output format ");
if (baton->formatOut == "input") { if (baton->formatOut == "input") {
(baton->err).append("when trying to match input format of "); unsupported.append("when trying to match input format of ");
(baton->err).append(ImageTypeId(inputImageType)); unsupported.append(ImageTypeId(inputImageType));
} else { } else {
(baton->err).append(baton->formatOut); unsupported.append(baton->formatOut);
} }
return Error(); throw std::runtime_error(unsupported);
} }
} else { } else {
// File output // File output
@@ -1168,6 +1177,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->webpEffort) ->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize) ->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed) ->set("mixed", baton->webpMixed)
->set("exact", baton->webpExact)
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) || } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1223,6 +1233,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("compression", baton->heifCompression) ->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort) ->set("effort", baton->heifEffort)
->set("bitdepth", baton->heifBitdepth) ->set("bitdepth", baton->heifBitdepth)
->set("tune", baton->heifTune.c_str())
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless)); ->set("lossless", baton->heifLossless));
@@ -1262,7 +1273,7 @@ class PipelineWorker : public Napi::AsyncWorker {
return Error(); return Error();
} }
} }
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
char const *what = err.what(); char const *what = err.what();
if (what && what[0]) { if (what && what[0]) {
(baton->err).append(what); (baton->err).append(what);
@@ -1294,7 +1305,6 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
int width = baton->width; int width = baton->width;
int height = baton->height; int height = baton->height;
@@ -1337,12 +1347,21 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Add buffer size to info
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength)); info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
// Pass ownership of output data to Buffer instance if (baton->typedArrayOut) {
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut), // ECMAScript ArrayBuffer with Uint8Array view
baton->bufferOutLength, sharp::FreeCallback); Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
Callback().Call(Receiver().Value(), { env.Null(), data, info }); memcpy(ab.Data(), baton->bufferOut, baton->bufferOutLength);
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
Napi::TypedArrayOf<uint8_t> data = Napi::TypedArrayOf<uint8_t>::New(env,
baton->bufferOutLength, ab, 0, napi_uint8_array);
Callback().Call(Receiver().Value(), { env.Null(), data, info });
} else {
// Node.js Buffer
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().Call(Receiver().Value(), { env.Null(), data, info });
}
} else { } else {
// Add file size to info // Add file size to info
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) { if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
@@ -1386,7 +1405,7 @@ class PipelineWorker : public Napi::AsyncWorker {
void MultiPageUnsupported(int const pages, std::string op) { void MultiPageUnsupported(int const pages, std::string op) {
if (pages > 1) { if (pages > 1) {
throw vips::VError(op + " is not supported for multi-page images"); throw std::runtime_error(op + " is not supported for multi-page images");
} }
} }
@@ -1469,6 +1488,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)}, {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
{"min_size", baton->webpMinSize ? "true" : "false"}, {"min_size", baton->webpMinSize ? "true" : "false"},
{"mixed", baton->webpMixed ? "true" : "false"}, {"mixed", baton->webpMixed ? "true" : "false"},
{"exact", baton->webpExact ? "true" : "false"},
{"effort", std::to_string(baton->webpEffort)} {"effort", std::to_string(baton->webpEffort)}
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
@@ -1615,6 +1635,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground"); baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt"); baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
baton->gamma = sharp::AttrAsDouble(options, "gamma"); baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA"); baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1692,6 +1713,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Output // Output
baton->formatOut = sharp::AttrAsStr(options, "formatOut"); baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata"); baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity"); baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
@@ -1706,6 +1728,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
} }
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
baton->withXmp = sharp::AttrAsStr(options, "withXmp"); baton->withXmp = sharp::AttrAsStr(options, "withXmp");
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
baton->loop = sharp::AttrAsUint32(options, "loop"); baton->loop = sharp::AttrAsUint32(options, "loop");
baton->delay = sharp::AttrAsInt32Vector(options, "delay"); baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1741,6 +1764,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort"); baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize"); baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed"); baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth"); baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort"); baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
@@ -1775,6 +1799,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort"); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth"); baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance"); baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier"); baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort"); baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");

View File

@@ -48,6 +48,7 @@ struct PipelineBaton {
size_t bufferOutLength; size_t bufferOutLength;
int pageHeightOut; int pageHeightOut;
int pagesOut; int pagesOut;
bool typedArrayOut;
std::vector<Composite *> composite; std::vector<Composite *> composite;
std::vector<sharp::InputDescriptor *> joinChannelIn; std::vector<sharp::InputDescriptor *> joinChannelIn;
int topOffsetPre; int topOffsetPre;
@@ -101,6 +102,7 @@ struct PipelineBaton {
bool trimLineArt; bool trimLineArt;
int trimOffsetLeft; int trimOffsetLeft;
int trimOffsetTop; int trimOffsetTop;
int trimMargin;
std::vector<double> linearA; std::vector<double> linearA;
std::vector<double> linearB; std::vector<double> linearB;
int dilateWidth; int dilateWidth;
@@ -167,6 +169,7 @@ struct PipelineBaton {
int webpEffort; int webpEffort;
bool webpMinSize; bool webpMinSize;
bool webpMixed; bool webpMixed;
bool webpExact;
int gifBitdepth; int gifBitdepth;
int gifEffort; int gifEffort;
double gifDither; double gifDither;
@@ -194,6 +197,7 @@ struct PipelineBaton {
std::string heifChromaSubsampling; std::string heifChromaSubsampling;
bool heifLossless; bool heifLossless;
int heifBitdepth; int heifBitdepth;
std::string heifTune;
double jxlDistance; double jxlDistance;
int jxlDecodingTier; int jxlDecodingTier;
int jxlEffort; int jxlEffort;
@@ -208,6 +212,7 @@ struct PipelineBaton {
std::unordered_map<std::string, std::string> withExif; std::unordered_map<std::string, std::string> withExif;
bool withExifMerge; bool withExifMerge;
std::string withXmp; std::string withXmp;
bool withGainMap;
int timeoutSeconds; int timeoutSeconds;
std::vector<double> convKernel; std::vector<double> convKernel;
int convKernelWidth; int convKernelWidth;
@@ -242,6 +247,7 @@ struct PipelineBaton {
bufferOutLength(0), bufferOutLength(0),
pageHeightOut(0), pageHeightOut(0),
pagesOut(0), pagesOut(0),
typedArrayOut(false),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),
@@ -281,6 +287,7 @@ struct PipelineBaton {
trimLineArt(false), trimLineArt(false),
trimOffsetLeft(0), trimOffsetLeft(0),
trimOffsetTop(0), trimOffsetTop(0),
trimMargin(0),
linearA{}, linearA{},
linearB{}, linearB{},
dilateWidth(0), dilateWidth(0),
@@ -344,6 +351,7 @@ struct PipelineBaton {
webpEffort(4), webpEffort(4),
webpMinSize(false), webpMinSize(false),
webpMixed(false), webpMixed(false),
webpExact(false),
gifBitdepth(8), gifBitdepth(8),
gifEffort(7), gifEffort(7),
gifDither(1.0), gifDither(1.0),
@@ -357,7 +365,7 @@ struct PipelineBaton {
tiffBigtiff(false), tiffBigtiff(false),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false), tiffPyramid(false),
tiffBitdepth(8), tiffBitdepth(0),
tiffMiniswhite(false), tiffMiniswhite(false),
tiffTile(false), tiffTile(false),
tiffTileHeight(256), tiffTileHeight(256),
@@ -371,6 +379,7 @@ struct PipelineBaton {
heifChromaSubsampling("4:4:4"), heifChromaSubsampling("4:4:4"),
heifLossless(false), heifLossless(false),
heifBitdepth(8), heifBitdepth(8),
heifTune("ssim"),
jxlDistance(1.0), jxlDistance(1.0),
jxlDecodingTier(0), jxlDecodingTier(0),
jxlEffort(7), jxlEffort(7),
@@ -381,6 +390,7 @@ struct PipelineBaton {
withMetadataOrientation(-1), withMetadataOrientation(-1),
withMetadataDensity(0.0), withMetadataDensity(0.0),
withExifMerge(true), withExifMerge(true),
withGainMap(false),
timeoutSeconds(0), timeoutSeconds(0),
convKernelWidth(0), convKernelWidth(0),
convKernelHeight(0), convKernelHeight(0),

View File

@@ -39,7 +39,7 @@ class StatsWorker : public Napi::AsyncWorker {
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
if (imageType != sharp::ImageType::UNKNOWN) { if (imageType != sharp::ImageType::UNKNOWN) {
@@ -92,7 +92,7 @@ class StatsWorker : public Napi::AsyncWorker {
baton->dominantRed = dx * 16 + 8; baton->dominantRed = dx * 16 + 8;
baton->dominantGreen = dy * 16 + 8; baton->dominantGreen = dy * 16 + 8;
baton->dominantBlue = dz * 16 + 8; baton->dominantBlue = dz * 16 + 8;
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
} }
@@ -112,7 +112,6 @@ class StatsWorker : public Napi::AsyncWorker {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
// Stats Object // Stats Object
Napi::Object info = Napi::Object::New(env); Napi::Object info = Napi::Object::New(env);

View File

@@ -123,6 +123,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw"
}) { }) {
std::string id = f == "jp2k" ? "jp2" : f;
// Input // Input
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str()); const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc); Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
@@ -154,11 +155,11 @@ Napi::Value format(const Napi::CallbackInfo& info) {
output.Set("stream", hasOutputBuffer); output.Set("stream", hasOutputBuffer);
// Other attributes // Other attributes
Napi::Object container = Napi::Object::New(env); Napi::Object container = Napi::Object::New(env);
container.Set("id", f); container.Set("id", id);
container.Set("input", input); container.Set("input", input);
container.Set("output", output); container.Set("output", output);
// Add to set of formats // Add to set of formats
format.Set(f, container); format.Set(id, container);
} }
// Raw, uncompressed data // Raw, uncompressed data
@@ -243,7 +244,7 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
} }
// Calculate colour distance // Calculate colour distance
maxColourDistance = image1.dE00(image2).max(); maxColourDistance = image1.dE00(image2).max();
} catch (vips::VError const &err) { } catch (std::runtime_error const &err) {
throw Napi::Error::New(env, err.what()); throw Napi::Error::New(env, err.what());
} }

View File

@@ -228,6 +228,19 @@ async.series({
} }
}); });
} }
}).add('sharp-buffer-uint8array', {
defer: true,
fn: (deferred) => {
sharp(inputJpgBuffer)
.resize(width, height)
.toUint8Array()
.then(() => {
deferred.resolve();
})
.catch((err) => {
throw err;
});
}
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
fn: (deferred) => { fn: (deferred) => {
@@ -266,6 +279,19 @@ async.series({
} }
}); });
} }
}).add('sharp-file-uint8array', {
defer: true,
fn: (deferred) => {
sharp(fixtures.inputJpg)
.resize(width, height)
.toUint8Array()
.then(() => {
deferred.resolve();
})
.catch((err) => {
throw err;
});
}
}).add('sharp-promise', { }).add('sharp-promise', {
defer: true, defer: true,
fn: (deferred) => { fn: (deferred) => {

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -70,9 +70,11 @@ module.exports = {
inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg
inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg
inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz
inputJpgWithGainMap: getPath('gain-map.jpg'), // https://github.com/libvips/libvips/issues/3799
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngGradients: getPath('gradients-rgb8.png'), inputPngGradients: getPath('gradients-rgb8.png'),
inputPngWithSlightGradientBorder: getPath('slight-gradient-border.png'),
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngCompleteTransparency: getPath('full-transparent.png'), inputPngCompleteTransparency: getPath('full-transparent.png'),
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'), inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
@@ -125,7 +127,7 @@ module.exports = {
inputSvgSmallViewBox: getPath('circle.svg'), inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputAvifWithPitmBox: getPath('pitm.avif'), // https://github.com/lovell/sharp/issues/4487
inputJPGBig: getPath('flowers.jpeg'), inputJPGBig: getPath('flowers.jpeg'),
inputPngDotAndLines: getPath('dot-and-lines.png'), inputPngDotAndLines: getPath('dot-and-lines.png'),

BIN
test/fixtures/pitm.avif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -86,6 +86,9 @@ let transformer = sharp()
}); });
readableStream.pipe(transformer).pipe(writableStream); readableStream.pipe(transformer).pipe(writableStream);
sharp().toUint8Array();
sharp().toUint8Array().then(({ data }) => data.byteLength);
console.log(sharp.format); console.log(sharp.format);
console.log(sharp.versions); console.log(sharp.versions);
@@ -231,7 +234,7 @@ sharp(input)
sharp(input) sharp(input)
.resize(100, 100) .resize(100, 100)
.toFormat('jpg') .toFormat('avif')
.toBuffer({ resolveWithObject: false }) .toBuffer({ resolveWithObject: false })
.then((outputBuffer: Buffer) => { .then((outputBuffer: Buffer) => {
// Resolves with a Buffer object when resolveWithObject is false // Resolves with a Buffer object when resolveWithObject is false
@@ -264,9 +267,7 @@ sharp(input)
// Output to tif // Output to tif
sharp(input) sharp(input)
.resize(100, 100) .resize(100, 100)
.toFormat('tif')
.toFormat('tiff') .toFormat('tiff')
.toFormat(sharp.format.tif)
.toFormat(sharp.format.tiff) .toFormat(sharp.format.tiff)
.toBuffer(); .toBuffer();
@@ -362,7 +363,7 @@ sharp(input)
.avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' }) .avif({ quality: 50, lossless: false, effort: 5, chromaSubsampling: '4:2:0' })
.heif() .heif()
.heif({}) .heif({})
.heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0' }) .heif({ quality: 50, compression: 'hevc', lossless: false, effort: 5, chromaSubsampling: '4:2:0', tune: 'psnr' })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
console.log(data); console.log(data);
@@ -545,8 +546,8 @@ sharp('input.tiff').jxl({ decodingTier: 4 }).toFile('out.jxl');
sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl'); sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl');
sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl'); sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl');
// Support `minSize` and `mixed` webp options // Support webp options
sharp('input.tiff').webp({ minSize: true, mixed: true }).toFile('out.gif'); sharp('input.tiff').webp({ minSize: true, mixed: true, exact: true }).toFile('out.webp');
// 'failOn' input param // 'failOn' input param
sharp('input.tiff', { failOn: 'none' }); sharp('input.tiff', { failOn: 'none' });
@@ -598,7 +599,7 @@ const vertexSplitQuadraticBasisSpline: string = sharp.interpolators.vertexSplitQ
// Triming // Triming
sharp(input).trim({ background: '#000' }).toBuffer(); sharp(input).trim({ background: '#000' }).toBuffer();
sharp(input).trim({ threshold: 10, lineArt: true }).toBuffer(); sharp(input).trim({ threshold: 10, lineArt: true }).toBuffer();
sharp(input).trim({ background: '#bf1942', threshold: 30 }).toBuffer(); sharp(input).trim({ background: '#bf1942', threshold: 30, margin: 20 }).toBuffer();
// Text input // Text input
sharp({ sharp({
@@ -771,3 +772,35 @@ sharp().erode();
sharp().erode(1); sharp().erode(1);
sharp().dilate(); sharp().dilate();
sharp().dilate(1); sharp().dilate(1);
sharp.format.dcraw;
sharp.format.dz;
sharp.format.fits;
sharp.format.gif;
sharp.format.heif;
sharp.format.jp2;
sharp.format.jpeg;
sharp.format.jxl;
sharp.format.magick;
sharp.format.openslide;
sharp.format.pdf;
sharp.format.png;
sharp.format.ppm;
sharp.format.rad;
sharp.format.raw;
sharp.format.svg;
sharp.format.tiff;
sharp.format.vips;
sharp.format.webp;
// @ts-expect-error
sharp.format.avif;
// @ts-expect-error
sharp.format.input;
// @ts-expect-error
sharp.format.jp2k;
// @ts-expect-error
sharp.format.jpg;
// @ts-expect-error
sharp.format.tif;
// @ts-expect-error
sharp.format.v;

View File

@@ -7,7 +7,13 @@ const { describe, it } = require('node:test');
const assert = require('node:assert'); const assert = require('node:assert');
const sharp = require('../../'); const sharp = require('../../');
const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures'); const {
inputAvif,
inputAvifWithPitmBox,
inputJpg,
inputGifAnimated,
inputPng,
} = require('../fixtures');
describe('AVIF', () => { describe('AVIF', () => {
it('called without options does not throw an error', () => { it('called without options does not throw an error', () => {
@@ -17,22 +23,20 @@ describe('AVIF', () => {
}); });
it('can convert AVIF to JPEG', async () => { it('can convert AVIF to JPEG', async () => {
const data = await sharp(inputAvif) const data = await sharp(inputAvif).resize(32).jpeg().toBuffer();
.resize(32)
.jpeg()
.toBuffer();
const { size, ...metadata } = await sharp(data).metadata(); const { size, ...metadata } = await sharp(data).metadata();
void size; void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 13, height: 13,
width: 32 width: 32,
}, },
channels: 3, channels: 3,
chromaSubsampling: '4:2:0', chromaSubsampling: '4:2:0',
density: 72, density: 72,
depth: 'uchar', depth: 'uchar',
format: 'jpeg', format: 'jpeg',
mediaType: 'image/jpeg',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
// 32 / (2048 / 858) = 13.40625 // 32 / (2048 / 858) = 13.40625
@@ -41,7 +45,7 @@ describe('AVIF', () => {
isProgressive: false, isProgressive: false,
isPalette: false, isPalette: false,
space: 'srgb', space: 'srgb',
width: 32 width: 32,
}); });
}); });
@@ -55,12 +59,13 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 26, height: 26,
width: 32 width: 32,
}, },
channels: 3, channels: 3,
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 26, height: 26,
@@ -70,25 +75,54 @@ describe('AVIF', () => {
pagePrimary: 0, pagePrimary: 0,
pages: 1, pages: 1,
space: 'srgb', space: 'srgb',
width: 32 width: 32,
}); });
}); });
it('can passthrough AVIF', async () => { it('can convert PNG to lossless AVIF', async () => {
const data = await sharp(inputAvif) const data = await sharp(inputPng)
.resize(32) .resize(32)
.avif({ lossless: true, effort: 0 })
.toBuffer(); .toBuffer();
const { size, ...metadata } = await sharp(data).metadata(); const { size, ...metadata } = await sharp(data).metadata();
void size; void size;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 13, height: 24,
width: 32 width: 32,
}, },
channels: 3, channels: 3,
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false,
hasProfile: false,
height: 24,
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32,
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif).resize(32).toBuffer();
const { size, ...metadata } = await sharp(data).metadata();
void size;
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
width: 32,
},
channels: 3,
compression: 'av1',
depth: 'uchar',
format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 13, height: 13,
@@ -98,7 +132,7 @@ describe('AVIF', () => {
pagePrimary: 0, pagePrimary: 0,
pages: 1, pages: 1,
space: 'srgb', space: 'srgb',
width: 32 width: 32,
}); });
}); });
@@ -112,12 +146,13 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 300, height: 300,
width: 10 width: 10,
}, },
channels: 4, channels: 4,
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: true, hasAlpha: true,
hasProfile: false, hasProfile: false,
height: 300, height: 300,
@@ -127,7 +162,7 @@ describe('AVIF', () => {
pagePrimary: 0, pagePrimary: 0,
pages: 1, pages: 1,
space: 'srgb', space: 'srgb',
width: 10 width: 10,
}); });
}); });
@@ -142,12 +177,13 @@ describe('AVIF', () => {
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
autoOrient: { autoOrient: {
height: 26, height: 26,
width: 32 width: 32,
}, },
channels: 3, channels: 3,
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 26, height: 26,
@@ -157,28 +193,91 @@ describe('AVIF', () => {
pagePrimary: 0, pagePrimary: 0,
pages: 1, pages: 1,
space: 'srgb', space: 'srgb',
width: 32 width: 32,
}); });
}); });
it('Invalid width - too large', async () => it('Invalid width - too large', async () =>
assert.rejects( assert.rejects(
() => sharp({ create: { width: 16385, height: 16, channels: 3, background: 'red' } }).avif().toBuffer(), () =>
/Processed image is too large for the HEIF format/ sharp({
) create: { width: 16385, height: 16, channels: 3, background: 'red' },
); })
.avif()
.toBuffer(),
/Processed image is too large for the HEIF format/,
));
it('Invalid height - too large', async () => it('Invalid height - too large', async () =>
assert.rejects( assert.rejects(
() => sharp({ create: { width: 16, height: 16385, channels: 3, background: 'red' } }).avif().toBuffer(), () =>
/Processed image is too large for the HEIF format/ sharp({
) create: { width: 16, height: 16385, channels: 3, background: 'red' },
); })
.avif()
.toBuffer(),
/Processed image is too large for the HEIF format/,
));
it('Invalid bitdepth value throws error', () => it('Invalid bitdepth value throws error', () =>
assert.throws( assert.throws(
() => sharp().avif({ bitdepth: 11 }), () => sharp().avif({ bitdepth: 11 }),
/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);
});
it('AVIF with non-zero primary item uses it as default page', async () => {
const { exif, ...metadata } = await sharp(inputAvifWithPitmBox).metadata();
void exif;
assert.deepStrictEqual(metadata, {
format: 'heif',
mediaType: 'image/avif',
width: 4096,
height: 800,
space: 'srgb',
channels: 3,
depth: 'uchar',
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
pages: 5,
pagePrimary: 4,
compression: 'av1',
resolutionUnit: 'cm',
hasProfile: false,
hasAlpha: false,
autoOrient: { width: 4096, height: 800 },
});
const data = await sharp(inputAvifWithPitmBox)
.png({ compressionLevel: 0 })
.toBuffer();
const { size, ...pngMetadata } = await sharp(data).metadata();
assert.deepStrictEqual(pngMetadata, {
format: 'png',
mediaType: 'image/png',
width: 4096,
height: 800,
space: 'srgb',
channels: 3,
depth: 'uchar',
isProgressive: false,
isPalette: false,
bitsPerSample: 8,
hasProfile: false,
hasAlpha: false,
autoOrient: { width: 4096, height: 800 },
});
});
}); });

View File

@@ -54,23 +54,6 @@ describe('failOn', () => {
); );
}); });
it('deprecated failOnError', () => {
assert.doesNotThrow(
() => sharp({ failOnError: true })
);
assert.doesNotThrow(
() => sharp({ failOnError: false })
);
assert.throws(
() => sharp({ failOnError: 'zoinks' }),
/Expected boolean for failOnError but received zoinks of type string/
);
assert.throws(
() => sharp({ failOnError: 1 }),
/Expected boolean for failOnError but received 1 of type number/
);
});
it('returns errors to callback for truncated JPEG', (_t, done) => { it('returns errors to callback for truncated JPEG', (_t, done) => {
sharp(fixtures.inputJpgTruncated, { failOn: 'truncated' }).toBuffer((err, data, info) => { sharp(fixtures.inputJpgTruncated, { failOn: 'truncated' }).toBuffer((err, data, info) => {
assert.ok(err.message.includes('VipsJpeg: premature end of'), err); assert.ok(err.message.includes('VipsJpeg: premature end of'), err);

69
test/unit/gain-map.js Normal file
View File

@@ -0,0 +1,69 @@
/*!
Copyright 2013 Lovell Fuller and others.
SPDX-License-Identifier: Apache-2.0
*/
const { describe, it } = require('node:test');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Gain maps', () => {
it('Metadata contains gainMap', async (t) => {
t.plan(4);
const { format, gainMap } = await sharp(
fixtures.inputJpgWithGainMap,
).metadata();
t.assert.strictEqual(format, 'jpeg');
t.assert.strictEqual(typeof gainMap, 'object');
t.assert.ok(Buffer.isBuffer(gainMap.image));
t.assert.strictEqual(gainMap.image.length, 31738);
});
it('Can be regenerated', async (t) => {
t.plan(4);
const data = await sharp(fixtures.inputJpgWithGainMap)
.withGainMap()
.toBuffer();
const metadata = await sharp(data).metadata();
t.assert.strictEqual(metadata.format, 'jpeg');
t.assert.strictEqual(typeof metadata.gainMap, 'object');
t.assert.ok(Buffer.isBuffer(metadata.gainMap.image));
const {
format,
width,
height,
channels,
depth,
space,
hasProfile,
chromaSubsampling,
} = await sharp(metadata.gainMap.image).metadata();
t.assert.deepEqual(
{
format,
width,
height,
channels,
depth,
space,
hasProfile,
chromaSubsampling,
},
{
format: 'jpeg',
width: 1920,
height: 1080,
channels: 1,
depth: 'uchar',
space: 'b-w',
hasProfile: true,
chromaSubsampling: '4:4:4',
},
);
});
});

View File

@@ -96,4 +96,14 @@ describe('HEIF', () => {
sharp().heif({ compression: 'av1', bitdepth: 11 }); sharp().heif({ compression: 'av1', bitdepth: 11 });
}, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/); }, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
}); });
it('valid tune does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ compression: 'hevc', tune: 'psnr' });
});
});
it('invalid tune should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 'hevc', tune: 'fail' });
}, /Error: Expected one of: psnr, ssim, iq for tune but received fail of type string/);
});
}); });

View File

@@ -7,6 +7,7 @@ const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const { afterEach, beforeEach, describe, it } = require('node:test'); const { afterEach, beforeEach, describe, it } = require('node:test');
const assert = require('node:assert'); const assert = require('node:assert');
const { isMarkedAsUntransferable } = require('node:worker_threads');
const sharp = require('../../'); const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
@@ -1092,4 +1093,39 @@ describe('Input/output', () => {
assert.strictEqual(channels, 3); assert.strictEqual(channels, 3);
assert.strictEqual(format, 'jpeg'); assert.strictEqual(format, 'jpeg');
}); });
it('toBuffer resolves with an untransferable Buffer', async () => {
const data = await sharp(fixtures.inputJpg)
.resize({ width: 8, height: 8 })
.toBuffer();
if (isMarkedAsUntransferable) {
assert.strictEqual(isMarkedAsUntransferable(data.buffer), true);
}
assert.strictEqual(ArrayBuffer.isView(data), true);
assert.strictEqual(ArrayBuffer.isView(data.buffer), false);
});
it('toUint8Array resolves with a transferable Uint8Array', async () => {
const { data, info } = await sharp(fixtures.inputJpg)
.resize({ width: 8, height: 8 })
.toUint8Array();
assert.strictEqual(data instanceof Uint8Array, true);
if (isMarkedAsUntransferable) {
assert.strictEqual(isMarkedAsUntransferable(data.buffer), false);
}
assert.strictEqual(ArrayBuffer.isView(data), true);
assert.strictEqual(info.format, 'jpeg');
assert.strictEqual(info.width, 8);
assert.strictEqual(info.height, 8);
assert.strictEqual(data.byteLength, info.size);
assert.strictEqual(data[0], 0xFF);
assert.strictEqual(data[1], 0xD8);
const metadata = await sharp(data).metadata();
assert.strictEqual(metadata.format, 'jpeg');
assert.strictEqual(metadata.width, 8);
assert.strictEqual(metadata.height, 8);
});
}); });

View File

@@ -11,7 +11,7 @@ const sharp = require('../../');
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
describe('JP2 output', () => { describe('JP2 output', () => {
if (!sharp.format.jp2k.input.buffer) { if (!sharp.format.jp2.input.buffer) {
it('JP2 output should fail due to missing OpenJPEG', () => it('JP2 output should fail due to missing OpenJPEG', () =>
assert.rejects(async () => assert.rejects(async () =>
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
@@ -45,11 +45,11 @@ describe('JP2 output', () => {
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(8, info.width); assert.strictEqual(8, info.width);
assert.strictEqual(15, info.height); assert.strictEqual(15, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(3, info.channels);
}); });
}); });
it('JP2 quality', (done) => { it('JP2 quality', (_t, done) => {
sharp(fixtures.inputJp2) sharp(fixtures.inputJp2)
.resize(320, 240) .resize(320, 240)
.jp2({ quality: 70 }) .jp2({ quality: 70 })
@@ -65,7 +65,7 @@ describe('JP2 output', () => {
}); });
}); });
it('Without chroma subsampling generates larger file', (done) => { it('Without chroma subsampling generates larger file', (_t, done) => {
// First generate with chroma subsampling (default) // First generate with chroma subsampling (default)
sharp(fixtures.inputJp2) sharp(fixtures.inputJp2)
.resize(320, 240) .resize(320, 240)
@@ -111,7 +111,7 @@ describe('JP2 output', () => {
it('Invalid JP2 chromaSubsampling value throws error', () => { it('Invalid JP2 chromaSubsampling value throws error', () => {
assert.throws( assert.throws(
() => sharp().jp2({ chromaSubsampling: '4:2:2' }), () => sharp().jp2({ chromaSubsampling: '4:2:2' }),
/Expected one of 4:2:0, 4:4:4 but received 4:2:2 of type string/ /Expected one of: 4:2:0, 4:4:4 for chromaSubsampling but received 4:2:2 of type string/
); );
}); });
} }

View File

@@ -180,7 +180,7 @@ describe('libvips binaries', () => {
process.env.npm_config_arch = 's390x'; process.env.npm_config_arch = 's390x';
process.env.npm_config_libc = ''; process.env.npm_config_libc = '';
const locatorHash = libvips.yarnLocator(); const locatorHash = libvips.yarnLocator();
assert.strictEqual(locatorHash, '4ab19140fd'); assert.strictEqual(locatorHash, 'f7e557e9d6');
delete process.env.npm_config_platform; delete process.env.npm_config_platform;
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
delete process.env.npm_config_libc; delete process.env.npm_config_libc;

View File

@@ -19,6 +19,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputJpg).metadata((err, metadata) => { sharp(fixtures.inputJpg).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -41,6 +42,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => { sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(450, metadata.width); assert.strictEqual(450, metadata.width);
assert.strictEqual(600, metadata.height); assert.strictEqual(600, metadata.height);
@@ -66,6 +68,7 @@ describe('Image metadata', () => {
const profile = icc.parse(metadata.icc); const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile); assert.strictEqual('object', typeof profile);
assert.strictEqual('Generic RGB Profile', profile.description); assert.strictEqual('Generic RGB Profile', profile.description);
assert.strictEqual('image/jpeg', metadata.mediaType);
done(); done();
}); });
}); });
@@ -92,6 +95,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputTiff).metadata((err, metadata) => { sharp(fixtures.inputTiff).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', metadata.format); assert.strictEqual('tiff', metadata.format);
assert.strictEqual('image/tiff', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2464, metadata.width); assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height); assert.strictEqual(3248, metadata.height);
@@ -111,6 +115,7 @@ describe('Image metadata', () => {
assert.strictEqual('undefined', typeof metadata.xmp); assert.strictEqual('undefined', typeof metadata.xmp);
assert.strictEqual('undefined', typeof metadata.xmpAsString); assert.strictEqual('undefined', typeof metadata.xmpAsString);
assert.strictEqual('inch', metadata.resolutionUnit); assert.strictEqual('inch', metadata.resolutionUnit);
assert.strictEqual('image/tiff', metadata.mediaType);
done(); done();
}); });
}); });
@@ -119,6 +124,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => { sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', metadata.format); assert.strictEqual('tiff', metadata.format);
assert.strictEqual('image/tiff', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2464, metadata.width); assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height); assert.strictEqual(3248, metadata.height);
@@ -142,6 +148,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPng).metadata((err, metadata) => { sharp(fixtures.inputPng).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2809, metadata.width); assert.strictEqual(2809, metadata.width);
assert.strictEqual(2074, metadata.height); assert.strictEqual(2074, metadata.height);
@@ -166,6 +173,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => { sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(320, metadata.width); assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height); assert.strictEqual(240, metadata.height);
@@ -191,6 +199,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => { sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2048, metadata.width); assert.strictEqual(2048, metadata.width);
assert.strictEqual(1536, metadata.height); assert.strictEqual(1536, metadata.height);
@@ -225,6 +234,7 @@ describe('Image metadata', () => {
height: 32, height: 32,
isPalette: false, isPalette: false,
isProgressive: false, isProgressive: false,
mediaType: 'image/png',
space: 'b-w', space: 'b-w',
width: 32, width: 32,
autoOrient: { autoOrient: {
@@ -250,6 +260,7 @@ describe('Image metadata', () => {
height: 32, height: 32,
isPalette: false, isPalette: false,
isProgressive: false, isProgressive: false,
mediaType: 'image/png',
space: 'grey16', space: 'grey16',
width: 32, width: 32,
autoOrient: { autoOrient: {
@@ -263,6 +274,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebP).metadata((err, metadata) => { sharp(fixtures.inputWebP).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('webp', metadata.format); assert.strictEqual('webp', metadata.format);
assert.strictEqual('image/webp', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(1024, metadata.width); assert.strictEqual(1024, metadata.width);
assert.strictEqual(772, metadata.height); assert.strictEqual(772, metadata.height);
@@ -285,11 +297,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimated) sharp(fixtures.inputWebPAnimated)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 80); assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -308,11 +321,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimated, { pages: -1 }) sharp(fixtures.inputWebPAnimated, { pages: -1 })
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay, isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha hasProfile, hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 720); assert.strictEqual(height, 720);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -332,11 +346,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimatedLoop3) sharp(fixtures.inputWebPAnimatedLoop3)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 370); assert.strictEqual(width, 370);
assert.strictEqual(height, 285); assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -355,6 +370,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputGif).metadata((err, metadata) => { sharp(fixtures.inputGif).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('gif', metadata.format); assert.strictEqual('gif', metadata.format);
assert.strictEqual('image/gif', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(800, metadata.width); assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height); assert.strictEqual(533, metadata.height);
@@ -375,6 +391,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => { sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('gif', metadata.format); assert.strictEqual('gif', metadata.format);
assert.strictEqual('image/gif', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2, metadata.width); assert.strictEqual(2, metadata.width);
assert.strictEqual(1, metadata.height); assert.strictEqual(1, metadata.height);
@@ -395,11 +412,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifAnimated) sharp(fixtures.inputGifAnimated)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, background, isProgressive, pages, loop, delay, background,
hasProfile, hasAlpha hasProfile, hasAlpha
}) => { }) => {
assert.strictEqual(format, 'gif'); assert.strictEqual(format, 'gif');
assert.strictEqual(mediaType, 'image/gif');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 80); assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -419,11 +437,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifAnimatedLoop3) sharp(fixtures.inputGifAnimatedLoop3)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'gif'); assert.strictEqual(format, 'gif');
assert.strictEqual(mediaType, 'image/gif');
assert.strictEqual(width, 370); assert.strictEqual(width, 370);
assert.strictEqual(height, 285); assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -462,6 +481,7 @@ describe('Image metadata', () => {
it('File in, Promise out', (_t, done) => { it('File in, Promise out', (_t, done) => {
sharp(fixtures.inputJpg).metadata().then((metadata) => { sharp(fixtures.inputJpg).metadata().then((metadata) => {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -508,6 +528,7 @@ describe('Image metadata', () => {
const pipeline = sharp(); const pipeline = sharp();
pipeline.metadata().then((metadata) => { pipeline.metadata().then((metadata) => {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual(829183, metadata.size); assert.strictEqual(829183, metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -559,6 +580,7 @@ describe('Image metadata', () => {
const pipeline = sharp().metadata((err, metadata) => { const pipeline = sharp().metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual(829183, metadata.size); assert.strictEqual(829183, metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -583,6 +605,7 @@ describe('Image metadata', () => {
image.metadata((err, metadata) => { image.metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -917,6 +940,7 @@ describe('Image metadata', () => {
.metadata() .metadata()
.then(metadata => { .then(metadata => {
assert.strictEqual(metadata.format, 'tiff'); assert.strictEqual(metadata.format, 'tiff');
assert.strictEqual(metadata.mediaType, 'image/tiff');
assert.strictEqual(metadata.width, 317); assert.strictEqual(metadata.width, 317);
assert.strictEqual(metadata.height, 211); assert.strictEqual(metadata.height, 211);
assert.strictEqual(metadata.space, 'rgb16'); assert.strictEqual(metadata.space, 'rgb16');
@@ -931,6 +955,7 @@ describe('Image metadata', () => {
const metadata = await sharp(fixtures.inputAvif).metadata(); const metadata = await sharp(fixtures.inputAvif).metadata();
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
format: 'heif', format: 'heif',
mediaType: 'image/avif',
width: 2048, width: 2048,
height: 858, height: 858,
space: 'srgb', space: 'srgb',
@@ -1014,6 +1039,7 @@ describe('Image metadata', () => {
const metadata = await sharp(fixtures.inputJpgLossless).metadata(); const metadata = await sharp(fixtures.inputJpgLossless).metadata();
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
format: 'jpeg', format: 'jpeg',
mediaType: 'image/jpeg',
width: 227, width: 227,
height: 149, height: 149,
space: 'srgb', space: 'srgb',

View File

@@ -145,6 +145,7 @@ describe('PNG', () => {
width: 68 width: 68
}, },
format: 'png', format: 'png',
mediaType: 'image/png',
width: 68, width: 68,
height: 68, height: 68,
space: 'srgb', space: 'srgb',
@@ -154,7 +155,6 @@ describe('PNG', () => {
isProgressive: false, isProgressive: false,
isPalette: true, isPalette: true,
bitsPerSample: 8, bitsPerSample: 8,
paletteBitDepth: 8,
hasProfile: false, hasProfile: false,
hasAlpha: false hasAlpha: false
}); });
@@ -226,12 +226,10 @@ describe('PNG', () => {
.png({ colours: 2, palette: false }) .png({ colours: 2, palette: false })
.toBuffer(); .toBuffer();
const { channels, isPalette, bitsPerSample, paletteBitDepth, size, space } = await sharp(data).metadata(); const { channels, isPalette, bitsPerSample, space } = await sharp(data).metadata();
assert.strictEqual(channels, 1); assert.strictEqual(channels, 1);
assert.strictEqual(isPalette, false); assert.strictEqual(isPalette, false);
assert.strictEqual(bitsPerSample, 1); assert.strictEqual(bitsPerSample, 1);
assert.strictEqual(paletteBitDepth, undefined);
assert.strictEqual(size, 89);
assert.strictEqual(space, 'b-w'); assert.strictEqual(space, 'b-w');
}); });

View File

@@ -94,24 +94,6 @@ describe('Sharpen', () => {
}); });
}); });
it('invalid sigma', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).sharpen(-1.5);
});
});
it('invalid flat', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).sharpen(1, -1);
});
});
it('invalid jagged', () => {
assert.throws(() => {
sharp(fixtures.inputJpg).sharpen(1, 1, -1);
});
});
it('invalid options.sigma', () => assert.throws( it('invalid options.sigma', () => assert.throws(
() => sharp().sharpen({ sigma: -1 }), () => sharp().sharpen({ sigma: -1 }),
/Expected number between 0\.000001 and 10 for options\.sigma but received -1 of type number/ /Expected number between 0\.000001 and 10 for options\.sigma but received -1 of type number/
@@ -144,24 +126,23 @@ describe('Sharpen', () => {
it('sharpened image is larger than non-sharpened', (_t, done) => { it('sharpened image is larger than non-sharpened', (_t, done) => {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(32, 24)
.sharpen(false)
.toBuffer((err, notSharpened, info) => { .toBuffer((err, notSharpened, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, notSharpened.length > 0); assert.strictEqual(true, notSharpened.length > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(32, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(24, info.height);
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(32, 24)
.sharpen(true) .sharpen()
.toBuffer((err, sharpened, info) => { .toBuffer((err, sharpened, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, sharpened.length > 0); assert.strictEqual(true, sharpened.length > 0);
assert.strictEqual(true, sharpened.length > notSharpened.length); assert.strictEqual(true, sharpened.length > notSharpened.length);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(32, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(24, info.height);
done(); done();
}); });
}); });

View File

@@ -122,7 +122,6 @@ describe('TIFF', () => {
sharp(fixtures.inputTiff8BitDepth) sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images .toColourspace('b-w') // can only squash 1 band uchar images
.tiff({ .tiff({
bitdepth: 8,
compression: 'none', compression: 'none',
predictor: 'none' predictor: 'none'
}) })
@@ -154,7 +153,7 @@ describe('TIFF', () => {
it('Invalid TIFF bitdepth value throws error', () => { it('Invalid TIFF bitdepth value throws error', () => {
assert.throws(() => { assert.throws(() => {
sharp().tiff({ bitdepth: 3 }); sharp().tiff({ bitdepth: 3 });
}, /Error: Expected 1, 2, 4 or 8 for bitdepth but received 3 of type number/); }, /Error: Expected 1, 2 or 4 for bitdepth but received 3 of type number/);
}); });
it('TIFF setting xres and yres on file', () => it('TIFF setting xres and yres on file', () =>

View File

@@ -222,6 +222,9 @@ describe('Trim borders', () => {
}, },
'Invalid lineArt': { 'Invalid lineArt': {
lineArt: 'fail' lineArt: 'fail'
},
'Invalid margin': {
margin: -1
} }
}).forEach(([description, parameter]) => { }).forEach(([description, parameter]) => {
it(description, () => { it(description, () => {
@@ -289,4 +292,42 @@ describe('Trim borders', () => {
assert.strictEqual(trimOffsetLeft, 0); assert.strictEqual(trimOffsetLeft, 0);
}); });
}); });
describe('Adds margin around content', () => {
it('Should trim complex gradients', async () => {
const { info } = await sharp(fixtures.inputPngGradients)
.trim({ threshold: 50, margin: 100 })
.toBuffer({ resolveWithObject: true });
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
assert.strictEqual(width, 1000);
assert.strictEqual(height, 443);
assert.strictEqual(trimOffsetTop, -557);
assert.strictEqual(trimOffsetLeft, 0);
});
it('Should trim simple gradients', async () => {
const { info } = await sharp(fixtures.inputPngWithSlightGradientBorder)
.trim({ threshold: 70, margin: 50 })
.toBuffer({ resolveWithObject: true });
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
assert.strictEqual(width, 900);
assert.strictEqual(height, 900);
assert.strictEqual(trimOffsetTop, -50);
assert.strictEqual(trimOffsetLeft, -50);
});
it('Should not overflow image bounding box', async () => {
const { info } = await sharp(fixtures.inputPngWithSlightGradientBorder)
.trim({ threshold: 70, margin: 9999999 })
.toBuffer({ resolveWithObject: true });
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
assert.strictEqual(width, 1000);
assert.strictEqual(height, 1000);
assert.strictEqual(trimOffsetTop, 0);
assert.strictEqual(trimOffsetLeft, 0);
});
});
}); });

View File

@@ -213,6 +213,33 @@ describe('WebP', () => {
); );
}); });
it('valid exact', () => {
assert.doesNotThrow(() => sharp().webp({ exact: true }));
});
it('invalid exact throws', () => {
assert.throws(
() => sharp().webp({ exact: 'fail' }),
/Expected boolean for webpExact but received fail of type string/
);
});
it('saving exact pixel colour values produces larger file size', async () => {
const withExact = await
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(8, 8)
.webp({ exact: true, effort: 0 })
.toBuffer();
const withoutExact = await
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(8, 8)
.webp({ exact: false, effort: 0 })
.toBuffer()
assert.strictEqual(true, withExact.length > withoutExact.length);
});
it('invalid loop throws', () => { it('invalid loop throws', () => {
assert.throws(() => { assert.throws(() => {
sharp().webp({ loop: -1 }); sharp().webp({ loop: -1 });