Compare commits

..

No commits in common. "main" and "v0.34.1" have entirely different histories.

71 changed files with 695 additions and 1607 deletions

105
.circleci/config.yml Normal file
View File

@ -0,0 +1,105 @@
version: 2.1
workflows:
build:
jobs:
- linux-arm64-glibc-node-18:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-18:
filters:
tags:
only: /^v.*/
- linux-arm64-glibc-node-20:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-20:
filters:
tags:
only: /^v.*/
jobs:
linux-arm64-glibc-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2204:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
sudo docker exec sharp sh -c "mkdir -p /etc/apt/keyrings"
sudo docker exec sharp sh -c "curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
sudo docker exec sharp sh -c "echo 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main' | tee /etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
- run: |
sudo docker exec sharp sh -c "npm run package-from-local-build"
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@img/sharp-linux-arm64=file:./npm/linux-arm64\""
sudo docker exec sharp sh -c "npm run clean"
sudo docker exec sharp sh -c "npm install --ignore-scripts"
sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"cd src && ln -s ../package.json && npx prebuild --upload=$prebuild_upload\" || true"
linux-arm64-glibc-node-20:
resource_class: arm.medium
machine:
image: ubuntu-2204:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl fonts-noto-core"
sudo docker exec sharp sh -c "mkdir -p /etc/apt/keyrings"
sudo docker exec sharp sh -c "curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg"
sudo docker exec sharp sh -c "echo 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main' | tee /etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
- run: |
sudo docker exec sharp sh -c "npm run package-from-local-build"
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@img/sharp-linux-arm64=file:./npm/linux-arm64\""
sudo docker exec sharp sh -c "npm run clean"
sudo docker exec sharp sh -c "npm install --ignore-scripts"
sudo docker exec sharp sh -c "npm test"
linux-arm64-musl-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2204:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:18-alpine3.17
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
- run: |
sudo docker exec sharp sh -c "npm run package-from-local-build"
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@img/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\""
sudo docker exec sharp sh -c "npm run clean"
sudo docker exec sharp sh -c "npm install --ignore-scripts"
sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"cd src && ln -s ../package.json && npx prebuild --upload=$prebuild_upload\" || true"
linux-arm64-musl-node-20:
resource_class: arm.medium
machine:
image: ubuntu-2204:current
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp node:20-alpine3.18
sudo docker exec sharp sh -c "apk add build-base git python3 font-noto --update-cache"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
- run: |
sudo docker exec sharp sh -c "npm run package-from-local-build"
sudo docker exec sharp sh -c "npm pkg set \"optionalDependencies.@img/sharp-linuxmusl-arm64=file:./npm/linuxmusl-arm64\""
sudo docker exec sharp sh -c "npm run clean"
sudo docker exec sharp sh -c "npm install --ignore-scripts"
sudo docker exec sharp sh -c "npm test"

View File

@ -1,5 +1,5 @@
freebsd_instance: freebsd_instance:
image_family: freebsd-15-0-snap image_family: freebsd-14-0-snap
task: task:
name: FreeBSD name: FreeBSD

View File

@ -4,10 +4,10 @@ on:
- pull_request - pull_request
permissions: {} permissions: {}
jobs: jobs:
build-native: github-runner:
permissions: permissions:
contents: read contents: write
name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
container: ${{ matrix.container }} container: ${{ matrix.container }}
strategy: strategy:
@ -20,7 +20,7 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: linux-x64 platform: linux-x64
package: true prebuild: true
- os: ubuntu-24.04 - os: ubuntu-24.04
container: rockylinux:8 container: rockylinux:8
nodejs_arch: x64 nodejs_arch: x64
@ -37,7 +37,7 @@ jobs:
container: node:18-alpine3.17 container: node:18-alpine3.17
nodejs_version_major: 18 nodejs_version_major: 18
platform: linuxmusl-x64 platform: linuxmusl-x64
package: true prebuild: true
- 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
@ -46,25 +46,12 @@ jobs:
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-arm
container: arm64v8/rockylinux:8
nodejs_arch: arm64
nodejs_version: "^18.17.0"
nodejs_version_major: 18
platform: linux-arm64
package: true
- os: ubuntu-24.04-arm
container: arm64v8/rockylinux:8
nodejs_arch: arm64
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: linux-arm64
- os: macos-13 - os: macos-13
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: darwin-x64 platform: darwin-x64
package: true prebuild: true
- os: macos-13 - os: macos-13
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
@ -80,7 +67,7 @@ jobs:
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: darwin-arm64 platform: darwin-arm64
package: true prebuild: true
- os: macos-14 - os: macos-14
nodejs_arch: arm64 nodejs_arch: arm64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
@ -91,55 +78,44 @@ jobs:
nodejs_version: "^22.9.0" nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: darwin-arm64 platform: darwin-arm64
- os: windows-2022 - os: windows-2019
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10 nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10
nodejs_version_major: 18 nodejs_version_major: 18
platform: win32-ia32 platform: win32-ia32
package: true prebuild: true
- os: windows-2022 - os: windows-2019
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: win32-ia32 platform: win32-ia32
- os: windows-2022 - os: windows-2019
nodejs_arch: x86 nodejs_arch: x86
nodejs_version: "^22.9.0" nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: win32-ia32 platform: win32-ia32
- os: windows-2022 - os: windows-2019
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: win32-x64 platform: win32-x64
package: true prebuild: true
- os: windows-2022 - os: windows-2019
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
platform: win32-x64 platform: win32-x64
- os: windows-2022 - os: windows-2019
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^22.9.0" nodejs_version: "^22.9.0"
nodejs_version_major: 22 nodejs_version_major: 22
platform: win32-x64 platform: win32-x64
- os: windows-11-arm
nodejs_arch: arm64
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: win32-arm64
package: true
- os: windows-11-arm
nodejs_arch: arm64
nodejs_version: "^22.9.0"
nodejs_version_major: 22
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')
run: | run: |
dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts
echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Linux musl) - name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine') if: contains(matrix.container, 'alpine')
run: apk add build-base git python3 font-noto --update-cache run: apk add build-base git python3 font-noto --update-cache
@ -154,70 +130,31 @@ jobs:
with: with:
node-version: ${{ matrix.nodejs_version }} node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }} architecture: ${{ matrix.nodejs_arch }}
- uses: actions/checkout@v4 - name: Checkout
uses: actions/checkout@v4
- name: Install - name: Install
run: npm install --build-from-source run: npm install --build-from-source
- name: Test - name: Test
run: npm test run: npm test
- name: Populate npm package - name: Test packaging
if: matrix.package
run: npm run package-from-local-build
- uses: actions/upload-artifact@v4
if: matrix.package
with:
name: ${{ matrix.platform }}
path: npm/${{ matrix.platform }}
retention-days: 1
if-no-files-found: error
build-linuxmusl-arm-64:
permissions:
contents: read
name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
runs-on: ubuntu-24.04-arm
container:
image: ${{ matrix.container }}
volumes:
- /:/host
strategy:
fail-fast: false
matrix:
include:
- container: node:18-alpine3.17
nodejs_version_major: 18
package: true
- container: node:20-alpine3.18
nodejs_version_major: 20
steps:
- name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757
shell: sh
run: | run: |
apk add nodejs npm run package-from-local-build
sed -i "s:ID=alpine:ID=NotpineForGHA:" /etc/os-release npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
cd /host/home/runner/runners/*/externals/ npm run clean
rm -rf node20/* npm install --ignore-scripts
mkdir node20/bin npm test
ln -s /usr/bin/node node20/bin/node - name: Prebuild
- name: Dependencies if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
run: apk add build-base git python3 font-noto --update-cache env:
- uses: actions/checkout@v4 prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
- name: Install run: |
run: npm install --build-from-source node -e "require('fs').cpSync('package.json', 'src/package.json')"
- name: Test cd src
run: npm test npx prebuild
- name: Populate npm package github-runner-qemu:
if: matrix.package
run: npm run package-from-local-build
- uses: actions/upload-artifact@v4
if: matrix.package
with:
name: linuxmusl-arm64
path: npm/linuxmusl-arm64
retention-days: 1
if-no-files-found: error
build-qemu:
permissions: permissions:
contents: read contents: write
name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]" name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} - prebuild
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
strategy: strategy:
fail-fast: false fail-fast: false
@ -250,6 +187,8 @@ jobs:
with: with:
arch: ${{ matrix.run_on_arch }} arch: ${{ matrix.run_on_arch }}
distro: ${{ matrix.distro }} distro: ${{ matrix.distro }}
env: |
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
run: | run: |
apt-get update apt-get update
apt-get install -y curl g++ git libatomic1 make python3 xz-utils apt-get install -y curl g++ git libatomic1 make python3 xz-utils
@ -259,20 +198,20 @@ jobs:
npm install --build-from-source npm install --build-from-source
npx mocha --no-config --spec=test/unit/io.js --timeout=30000 npx mocha --no-config --spec=test/unit/io.js --timeout=30000
npm run package-from-local-build npm run package-from-local-build
- uses: actions/upload-artifact@v4 npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
with: npm run clean
name: ${{ matrix.platform }} npm install --ignore-scripts
path: npm/${{ matrix.platform }} npx mocha --no-config --spec=test/unit/io.js --timeout=30000
retention-days: 1 [[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true
if-no-files-found: error github-runner-emscripten:
build-emscripten:
permissions: permissions:
contents: read contents: write
name: "build-wasm32 [package]" name: wasm32 - prebuild
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
container: "emscripten/emsdk:4.0.10" container: "emscripten/emsdk:4.0.6"
steps: steps:
- uses: actions/checkout@v4 - name: Checkout
uses: actions/checkout@v4
- name: Dependencies - name: Dependencies
run: apt-get update && apt-get install -y pkg-config run: apt-get update && apt-get install -y pkg-config
- name: Dependencies (Node.js) - name: Dependencies (Node.js)
@ -290,35 +229,17 @@ jobs:
test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
- name: Test - name: Test
run: emmake npm test run: emmake npm test
- name: Populate npm package - name: Test packaging
run: emmake npm run package-from-local-build run: |
- uses: actions/upload-artifact@v4 emmake npm run package-from-local-build
with: npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32"
name: wasm32 npm run clean
path: npm/wasm32 rm -rf node_modules/@img/sharp-linux-x64
retention-days: 1 npm install --cpu=wasm32
if-no-files-found: error npm test
release: - name: Prebuild
permissions: if: startsWith(github.ref, 'refs/tags/')
contents: write env:
runs-on: ubuntu-24.04 npm_config_nodedir: emscripten
needs: prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
- build-native run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0
- build-linuxmusl-arm-64
- build-qemu
- build-emscripten
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: npm
- name: Create npm workspace tarball
run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js .
- name: Create GitHub release for tag
if: startsWith(github.ref, 'refs/tags/v')
uses: ncipollo/release-action@v1
with:
artifacts: npm-workspace.tar.xz
artifactContentType: application/x-xz
prerelease: ${{ contains(github.ref, '-rc') }}
makeLatest: ${{ !contains(github.ref, '-rc') }}

View File

@ -70,27 +70,27 @@ jobs:
runtime: bun runtime: bun
- name: win32-x64-node-npm - name: win32-x64-node-npm
runs-on: windows-2022 runs-on: windows-2019
runtime: node runtime: node
package-manager: npm package-manager: npm
- name: win32-x64-node-pnpm - name: win32-x64-node-pnpm
runs-on: windows-2022 runs-on: windows-2019
runtime: node runtime: node
package-manager: pnpm package-manager: pnpm
- name: win32-x64-node-yarn - name: win32-x64-node-yarn
runs-on: windows-2022 runs-on: windows-2019
runtime: node runtime: node
package-manager: yarn package-manager: yarn
- name: win32-x64-node-yarn-pnp - name: win32-x64-node-yarn-pnp
runs-on: windows-2022 runs-on: windows-2019
runtime: node runtime: node
package-manager: yarn-pnp package-manager: yarn-pnp
- name: win32-x64-node-yarn-v1 - name: win32-x64-node-yarn-v1
runs-on: windows-2022 runs-on: windows-2019
runtime: node runtime: node
package-manager: yarn-v1 package-manager: yarn-v1
- name: win32-x64-deno - name: win32-x64-deno
runs-on: windows-2022 runs-on: windows-2019
runtime: deno runtime: deno
steps: steps:

6
.prebuildrc Normal file
View File

@ -0,0 +1,6 @@
{
"runtime": "napi",
"include-regex": "(sharp-.+\\.node|libvips-.+\\.dll)",
"prerelease": true,
"strip": true
}

View File

@ -1,6 +1,6 @@
# sharp # sharp
<img src="https://sharp.pixelplumbing.com/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node-API module The typical use case for this high speed Node-API module
is to convert large images in common formats to is to convert large images in common formats to

View File

@ -18,7 +18,7 @@ export default defineConfig({
tag: 'meta', tag: 'meta',
attrs: { attrs: {
'http-equiv': 'Content-Security-Policy', 'http-equiv': 'Content-Security-Policy',
content: "default-src 'self'; connect-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js/;" content: "default-src 'self'; connect-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.jsdelivr.net/gh/lovell/; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js/;"
} }
}, { }, {
tag: 'link', tag: 'link',
@ -70,10 +70,10 @@ export default defineConfig({
{ label: 'Performance', slug: 'performance' }, { label: 'Performance', slug: 'performance' },
{ label: 'Changelog', slug: 'changelog' } { label: 'Changelog', slug: 'changelog' }
], ],
social: [ social: {
{ icon: 'openCollective', label: 'Open Collective', href: 'https://opencollective.com/libvips' }, openCollective: 'https://opencollective.com/libvips',
{ icon: 'github', label: 'GitHub', href: 'https://github.com/lovell/sharp' } github: 'https://github.com/lovell/sharp'
] }
}) })
] ]
}); });

View File

@ -11,7 +11,7 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/starlight": "^0.34.3", "@astrojs/starlight": "^0.32.3",
"astro": "^5.7.13" "astro": "^5.5.3"
} }
} }

View File

@ -317,9 +317,3 @@ GitHub: https://github.com/florentzabera
Name: Quentin Pinçon Name: Quentin Pinçon
GitHub: https://github.com/qpincon GitHub: https://github.com/qpincon
Name: Hans Chen
GitHub: https://github.com/hans00
Name: Thibaut Patel
GitHub: https://github.com/tpatel

View File

@ -44,23 +44,24 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. | | [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. |
| [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. | | [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. |
| [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. | | [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. |
| [options.subifd] | <code>number</code> | <code>-1</code> | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. |
| [options.level] | <code>number</code> | <code>0</code> | level to extract from a multi-level input (OpenSlide), zero based. |
| [options.pdfBackground] | <code>string</code> \| <code>Object</code> | | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
| [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. | | [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. |
| [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. | | [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. |
| [options.raw.width] | <code>number</code> | | integral number of pixels wide. | | [options.raw.width] | <code>number</code> | | integral number of pixels wide. |
| [options.raw.height] | <code>number</code> | | integral number of pixels high. | | [options.raw.height] | <code>number</code> | | integral number of pixels high. |
| [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. | | [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. |
| [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) | | [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) |
| [options.raw.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`. |
| [options.create] | <code>Object</code> | | describes a new image to be created. | | [options.create] | <code>Object</code> | | describes a new image to be created. |
| [options.create.width] | <code>number</code> | | integral number of pixels wide. | | [options.create.width] | <code>number</code> | | integral number of pixels wide. |
| [options.create.height] | <code>number</code> | | integral number of pixels high. | | [options.create.height] | <code>number</code> | | integral number of pixels high. |
| [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). | | [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). |
| [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | | [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.create.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `create.height`. |
| [options.create.noise] | <code>Object</code> | | describes a noise to be created. | | [options.create.noise] | <code>Object</code> | | describes a noise to be created. |
| [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. | | [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. |
| [options.create.noise.mean] | <code>number</code> | <code>128</code> | Mean value of pixels in the generated noise. | | [options.create.noise.mean] | <code>number</code> | | mean of pixels in generated noise. |
| [options.create.noise.sigma] | <code>number</code> | <code>30</code> | Standard deviation of pixel values in the generated noise. | | [options.create.noise.sigma] | <code>number</code> | | standard deviation of pixels in generated noise. |
| [options.text] | <code>Object</code> | | describes a new text image to be created. | | [options.text] | <code>Object</code> | | describes a new text image to be created. |
| [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. | | [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
| [options.text.font] | <code>string</code> | | font name to render with. | | [options.text.font] | <code>string</code> | | font name to render with. |
@ -80,17 +81,6 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.join.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | | [options.join.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.join.halign] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). | | [options.join.halign] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). |
| [options.join.valign] | <code>string</code> | <code>&quot;&#x27;top&#x27;&quot;</code> | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). | | [options.join.valign] | <code>string</code> | <code>&quot;&#x27;top&#x27;&quot;</code> | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). |
| [options.tiff] | <code>Object</code> | | Describes TIFF specific options. |
| [options.tiff.subifd] | <code>number</code> | <code>-1</code> | Sub Image File Directory to extract for OME-TIFF, defaults to main image. |
| [options.svg] | <code>Object</code> | | Describes SVG specific options. |
| [options.svg.stylesheet] | <code>string</code> | | Custom CSS for SVG input, applied with a User Origin during the CSS cascade. |
| [options.svg.highBitdepth] | <code>boolean</code> | <code>false</code> | Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. |
| [options.pdf] | <code>Object</code> | | Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
| [options.pdf.background] | <code>string</code> \| <code>Object</code> | | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.openSlide] | <code>Object</code> | | Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide. |
| [options.openSlide.level] | <code>number</code> | <code>0</code> | Level to extract from a multi-level input, zero based. |
| [options.jp2] | <code>Object</code> | | Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG. |
| [options.jp2.oneshot] | <code>boolean</code> | <code>false</code> | Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. |
**Example** **Example**
```js ```js

View File

@ -242,57 +242,6 @@ const outputWithP3 = await sharp(input)
``` ```
## keepXmp
> keepXmp() ⇒ <code>Sharp</code>
Keep XMP metadata from the input image in the output image.
**Since**: 0.34.3
**Example**
```js
const outputWithXmp = await sharp(inputWithXmp)
.keepXmp()
.toBuffer();
```
## withXmp
> withXmp(xmp) ⇒ <code>Sharp</code>
Set XMP metadata in the output image.
Supported by PNG, JPEG, WebP, and TIFF output.
**Throws**:
- <code>Error</code> Invalid parameters
**Since**: 0.34.3
| Param | Type | Description |
| --- | --- | --- |
| xmp | <code>string</code> | String containing XMP metadata to be embedded in the output image. |
**Example**
```js
const xmpString = `
<?xml version="1.0"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>`;
const data = await sharp(input)
.withXmp(xmpString)
.toBuffer();
```
## keepMetadata ## keepMetadata
> keepMetadata() ⇒ <code>Sharp</code> > keepMetadata() ⇒ <code>Sharp</code>
@ -547,7 +496,6 @@ The palette of the input image will be re-used if possible.
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) | | [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 | | [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 | | [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
| [options.keepDuplicateFrames] | <code>boolean</code> | <code>false</code> | keep duplicate frames in the output instead of combining them |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation | | [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
| [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.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format | | [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |
@ -678,9 +626,6 @@ 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
and requires a CPU with ARM64v8.4 or later.
**Throws**: **Throws**:

View File

@ -17,7 +17,7 @@ When both a `width` and `height` are provided, the possible methods by which the
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property. Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="/api-resize-fit.svg"> <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are: When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
@ -38,8 +38,6 @@ Possible downsizing kernels are:
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf). - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
- `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
- `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators. When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
Downsampling kernels without a matching upsampling interpolator map to `cubic`. Downsampling kernels without a matching upsampling interpolator map to `cubic`.

View File

@ -113,9 +113,15 @@ Some image format libraries spawn additional threads,
e.g. libaom manages its own 4 threads when encoding AVIF images, e.g. libaom manages its own 4 threads when encoding AVIF images,
and these are independent of the value set here. and these are independent of the value set here.
:::note The maximum number of images that sharp can process in parallel
Further [control over performance](/performance) is available. is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
::: which defaults to 4.
https://nodejs.org/api/cli.html#uv_threadpool_sizesize
For example, by default, a machine with 8 CPU cores will process
4 images in parallel and use up to 8 threads per image,
so there will be up to 32 concurrent threads.
**Returns**: <code>number</code> - concurrency **Returns**: <code>number</code> - concurrency

View File

@ -4,64 +4,7 @@ title: Changelog
## v0.34 - *hat* ## v0.34 - *hat*
Requires libvips v8.17.1 Requires libvips v8.16.1
### v0.34.3 - TBD
* Upgrade to libvips v8.17.1 for upstream bug fixes.
* Add "Magic Kernel Sharp" (no relation) to resizing kernels.
* Deprecate top-level, format-specific constructor parameters, e.g. `subifd` becomes `tiff.subifd`.
* Expose `stylesheet` and `highBitdepth` SVG input parameters.
* Expose `keepDuplicateFrames` GIF output parameter.
* Add support for RAW digital camera image input. Requires libvips compiled with libraw support.
* Provide XMP metadata as a string, as well as a Buffer, where possible.
* Add `pageHeight` option to `create` and `raw` input for animated images.
[#3236](https://github.com/lovell/sharp/issues/3236)
* Expose JPEG 2000 `oneshot` decoder option.
[#4262](https://github.com/lovell/sharp/pull/4262)
[@mbklein](https://github.com/mbklein)
* Support composite operation with non-sRGB pipeline colourspace.
[#4412](https://github.com/lovell/sharp/pull/4412)
[@kleisauke](https://github.com/kleisauke)
* Add `keepXmp` and `withXmp` for control over output XMP metadata.
[#4416](https://github.com/lovell/sharp/pull/4416)
[@tpatel](https://github.com/tpatel)
### v0.34.2 - 20th May 2025
* Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0).
[#3394](https://github.com/lovell/sharp/issues/3394)
* Ensure `pdfBackground` constructor property is used.
[#4207](https://github.com/lovell/sharp/pull/4207)
[#4398](https://github.com/lovell/sharp/issues/4398)
* Add experimental support for prebuilt Windows ARM64 binaries.
[#4375](https://github.com/lovell/sharp/pull/4375)
[@hans00](https://github.com/hans00)
* Ensure resizing with a `fit` of `contain` supports multiple alpha channels.
[#4382](https://github.com/lovell/sharp/issues/4382)
* TypeScript: Ensure `metadata` response more closely matches reality.
[#4383](https://github.com/lovell/sharp/issues/4383)
* TypeScript: Ensure `smartDeblock` property is included in WebP definition.
[#4387](https://github.com/lovell/sharp/pull/4387)
[@Stephen-X](https://github.com/Stephen-X)
* Ensure support for wide-character filenames on Windows (regression in 0.34.0).
[#4391](https://github.com/lovell/sharp/issues/4391)
### v0.34.1 - 7th April 2025 ### v0.34.1 - 7th April 2025

View File

@ -2,7 +2,7 @@
title: "High performance Node.js image processing" title: "High performance Node.js image processing"
--- ---
<img src="/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node-API module The typical use case for this high speed Node-API module
is to convert large images in common formats to is to convert large images in common formats to

View File

@ -21,7 +21,7 @@ pnpm add sharp
``` ```
When using `pnpm`, you may need to add `sharp` to When using `pnpm`, you may need to add `sharp` to
[ignoredBuiltDependencies](https://pnpm.io/settings#ignoredbuiltdependencies) [ignoredBuiltDependencies](https://pnpm.io/package_json#pnpmignoredbuiltdependencies)
to silence warnings. to silence warnings.
```sh ```sh
@ -53,7 +53,6 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* 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
* Windows ARM64 (experimental, 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.
@ -94,7 +93,7 @@ Use the [supportedArchitectures](https://yarnpkg.com/configuration/yarnrc#suppor
### pnpm v8+ ### pnpm v8+
Use the [supportedArchitectures](https://pnpm.io/settings#supportedarchitectures) configuration. Use the [supportedArchitectures](https://pnpm.io/package_json#pnpmsupportedarchitectures) configuration.
## Custom libvips ## Custom libvips
@ -134,7 +133,7 @@ npm install --save node-addon-api node-gyp
``` ```
When using `pnpm`, you may need to add `sharp` to When using `pnpm`, you may need to add `sharp` to
[onlyBuiltDependencies](https://pnpm.io/settings#onlybuiltdependencies) [onlyBuiltDependencies](https://pnpm.io/package_json#pnpmonlybuiltdependencies)
to ensure the installation script can be run. to ensure the installation script can be run.
For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
@ -175,7 +174,7 @@ The default memory allocator on most glibc-based Linux systems
processes that involve lots of small memory allocations. processes that involve lots of small memory allocations.
For this reason, by default, sharp will limit the use of thread-based For this reason, by default, sharp will limit the use of thread-based
[concurrency](/api-utility#concurrency) when the glibc allocator is [concurrency](api-utility#concurrency) when the glibc allocator is
detected at runtime. detected at runtime.
To help avoid fragmentation and improve performance on these systems, To help avoid fragmentation and improve performance on these systems,

View File

@ -2,38 +2,6 @@
title: Performance title: Performance
--- ---
## Parallelism and concurrency
Node.js uses a libuv-managed thread pool when processing asynchronous calls to native modules such as sharp.
The maximum number of images that sharp can process in parallel is controlled by libuv's
[`UV_THREADPOOL_SIZE`](https://nodejs.org/api/cli.html#uv_threadpool_sizesize)
environment variable, which defaults to 4.
When using more than 4 physical CPU cores, set this environment variable
before the Node.js process starts to increase the thread pool size.
```sh
export UV_THREADPOOL_SIZE="$(lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l)"
```
libvips uses a glib-managed thread pool to avoid the overhead of spawning new threads.
The default number of threads used to concurrently process each image is the same as the number of CPU cores,
except when using glibc-based Linux without jemalloc, where the default is `1` to help reduce memory fragmentation.
Use [`sharp.concurrency()`](/api-utility/#concurrency) to manage the number of threads per image.
To reduce memory fragmentation when using the default Linux glibc memory allocator, set the
[`MALLOC_ARENA_MAX`](https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html)
environment variable before the Node.js process starts to reduce the number of memory pools.
```sh
export MALLOC_ARENA_MAX="2"
```
## Benchmark
A test to benchmark the performance of this module relative to alternatives. A test to benchmark the performance of this module relative to alternatives.
Greater libvips performance can be expected with caching enabled (default) Greater libvips performance can be expected with caching enabled (default)
@ -41,28 +9,28 @@ and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Contenders ## Contenders
- [jimp](https://www.npmjs.com/package/jimp) v1.6.0 - Image processing in pure JavaScript. * [jimp](https://www.npmjs.com/package/jimp) v1.6.0 - Image processing in pure JavaScript.
- [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "_has been unmaintained for a long time_". * [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
- [gm](https://www.npmjs.com/package/gm) v1.25.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility, but "_has been sunset_". * [gm](https://www.npmjs.com/package/gm) v1.25.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility, but "*has been sunset*".
- sharp v0.34.3 / libvips v8.17.0 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.34.0 / libvips v8.16.1 - Caching within libvips disabled to ensure a fair comparison.
### Environment ## Environment
#### AMD64 ### AMD64
- AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14) * AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14)
- Ubuntu 25.04 * Ubuntu 24.10 [fad5ba7223f8](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-fad5ba7223f8d87179dfa23211d31845d47e07a474ac31ad5258afb606523c0d)
- Node.js 24.3.0 * Node.js 22.14.0
#### ARM64 ### ARM64
- AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4) * AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4)
- Ubuntu 25.04 * Ubuntu 24.10 [133f2e05cb69](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-133f2e05cb6958c3ce7ec870fd5a864558ba780fb7062315b51a23670bff7e76)
- Node.js 24.3.0 * Node.js 22.14.0
### Task: JPEG ## Task: JPEG
Decompress a 2725x2225 JPEG image, Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available), resize to 720x588 using Lanczos 3 resampling (where available),
@ -72,31 +40,29 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead.
#### Results: JPEG (AMD64) #### Results: JPEG (AMD64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | 2.40 | 1.0 | | jimp | buffer | buffer | 2.35 | 1.0 |
| jimp | file | 2.60 | 1.1 | | imagemagick | file | file | 10.51 | 4.5 |
| imagemagick | file | 9.70 | 4.0 | | gm | buffer | buffer | 11.67 | 5.0 |
| gm | buffer | 11.60 | 4.8 | | gm | file | file | 11.75 | 5.1 |
| gm | file | 11.72 | 4.9 | | sharp | stream | stream | 60.72 | 25.8 |
| sharp | stream | 59.40 | 24.8 | | sharp | file | file | 62.37 | 26.5 |
| sharp | file | 62.67 | 26.1 | | sharp | buffer | buffer | 65.15 | 27.7 |
| sharp | buffer | 64.42 | 26.8 |
#### Results: JPEG (ARM64) #### Results: JPEG (ARM64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | 2.24 | 1.0 | | jimp | buffer | buffer | 2.13 | 1.0 |
| jimp | file | 2.47 | 1.1 | | imagemagick | file | file | 12.95 | 6.1 |
| imagemagick | file | 10.42 | 4.7 | | gm | buffer | buffer | 13.53 | 6.4 |
| gm | buffer | 12.80 | 5.7 | | gm | file | file | 13.52 | 6.4 |
| gm | file | 12.88 | 5.7 | | sharp | stream | stream | 46.58 | 21.9 |
| sharp | stream | 45.58 | 20.3 | | sharp | file | file | 48.42 | 22.7 |
| sharp | file | 47.99 | 21.4 | | sharp | buffer | buffer | 50.16 | 23.6 |
| sharp | buffer | 49.20 | 22.0 |
### Task: PNG ## Task: PNG
Decompress a 2048x1536 RGBA PNG image, Decompress a 2048x1536 RGBA PNG image,
premultiply the alpha channel, premultiply the alpha channel,
@ -106,25 +72,25 @@ and without adaptive filtering.
Note: jimp does not support premultiply/unpremultiply. Note: jimp does not support premultiply/unpremultiply.
#### Results: PNG (AMD64) ### Results: PNG (AMD64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| imagemagick | file | 6.06 | 1.0 | | gm | file | file | 8.66 | 1.0 |
| gm | file | 8.44 | 1.4 | | imagemagick | file | file | 8.79 | 1.0 |
| jimp | buffer | 10.98 | 1.8 | | jimp | buffer | buffer | 11.26 | 1.3 |
| sharp | file | 28.26 | 4.7 | | sharp | file | file | 27.93 | 3.2 |
| sharp | buffer | 28.70 | 4.7 | | sharp | buffer | buffer | 28.69 | 3.3 |
#### Results: PNG (ARM64) ### Results: PNG (ARM64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| imagemagick | file | 7.09 | 1.0 | | gm | file | file | 9.65 | 1.0 |
| gm | file | 8.93 | 1.3 | | imagemagick | file | file | 9.72 | 1.0 |
| jimp | buffer | 10.28 | 1.5 | | jimp | buffer | buffer | 10.68 | 1.1 |
| sharp | file | 23.81 | 3.4 | | sharp | file | file | 23.90 | 2.5 |
| sharp | buffer | 24.19 | 3.4 | | sharp | buffer | buffer | 24.48 | 2.5 |
## Running the benchmark test ## Running the benchmark test

View File

@ -134,26 +134,6 @@ function toColorspace (colorspace) {
return this.toColourspace(colorspace); return this.toColourspace(colorspace);
} }
/**
* Create a RGBA colour array from a given value.
* @private
* @param {string|Object} value
* @throws {Error} Invalid value
*/
function _getBackgroundColourOption (value) {
if (is.object(value) || is.string(value)) {
const colour = color(value);
return [
colour.red(),
colour.green(),
colour.blue(),
Math.round(colour.alpha() * 255)
];
} else {
throw is.invalidParameterError('background', 'object or string', value);
}
}
/** /**
* Update a colour attribute of the this.options Object. * Update a colour attribute of the this.options Object.
* @private * @private
@ -163,7 +143,17 @@ function _getBackgroundColourOption (value) {
*/ */
function _setBackgroundColourOption (key, value) { function _setBackgroundColourOption (key, value) {
if (is.defined(value)) { if (is.defined(value)) {
this.options[key] = _getBackgroundColourOption(value); if (is.object(value) || is.string(value)) {
const colour = color(value);
this.options[key] = [
colour.red(),
colour.green(),
colour.blue(),
Math.round(colour.alpha() * 255)
];
} else {
throw is.invalidParameterError('background', 'object or string', value);
}
} }
} }
@ -183,7 +173,6 @@ module.exports = function (Sharp) {
toColourspace, toColourspace,
toColorspace, toColorspace,
// Private // Private
_getBackgroundColourOption,
_setBackgroundColourOption _setBackgroundColourOption
}); });
// Class attributes // Class attributes

View File

@ -153,6 +153,9 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored. * @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
* @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. * @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
* @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. * @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {string|Object} [options.pdfBackground] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`.
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {number} [options.raw.width] - integral number of pixels wide. * @param {number} [options.raw.width] - integral number of pixels wide.
@ -160,17 +163,15 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4. * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true` * @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
* to avoid sharp premultiplying the image. (optional, default `false`) * to avoid sharp premultiplying the image. (optional, default `false`)
* @param {number} [options.raw.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`.
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {number} [options.create.width] - integral number of pixels wide. * @param {number} [options.create.width] - integral number of pixels wide.
* @param {number} [options.create.height] - integral number of pixels high. * @param {number} [options.create.height] - integral number of pixels high.
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA). * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {number} [options.create.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `create.height`.
* @param {Object} [options.create.noise] - describes a noise to be created. * @param {Object} [options.create.noise] - describes a noise to be created.
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported. * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean=128] - Mean value of pixels in the generated noise. * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma=30] - Standard deviation of pixel values in the generated noise. * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
* @param {Object} [options.text] - describes a new text image to be created. * @param {Object} [options.text] - describes a new text image to be created.
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
* @param {string} [options.text.font] - font name to render with. * @param {string} [options.text.font] - font name to render with.
@ -190,17 +191,7 @@ const debuglog = util.debuglog('sharp');
* @param {string|Object} [options.join.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.join.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). * @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`).
* @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). * @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
* @param {Object} [options.tiff] - Describes TIFF specific options. *
* @param {number} [options.tiff.subifd=-1] - Sub Image File Directory to extract for OME-TIFF, defaults to main image.
* @param {Object} [options.svg] - Describes SVG specific options.
* @param {string} [options.svg.stylesheet] - Custom CSS for SVG input, applied with a User Origin during the CSS cascade.
* @param {boolean} [options.svg.highBitdepth=false] - Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA.
* @param {Object} [options.pdf] - Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick.
* @param {string|Object} [options.pdf.background] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.openSlide] - Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide.
* @param {number} [options.openSlide.level=0] - Level to extract from a multi-level input, zero based.
* @param {Object} [options.jp2] - Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG.
* @param {boolean} [options.jp2.oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@ -306,9 +297,8 @@ const Sharp = function (input, options) {
withIccProfile: '', withIccProfile: '',
withExif: {}, withExif: {},
withExifMerge: true, withExifMerge: true,
withXmp: '',
resolveWithObject: false, resolveWithObject: false,
loop: -1, loop: 1,
delay: [], delay: [],
// output format // output format
jpegQuality: 80, jpegQuality: 80,
@ -347,7 +337,6 @@ const Sharp = function (input, options) {
gifDither: 1, gifDither: 1,
gifInterFrameMaxError: 0, gifInterFrameMaxError: 0,
gifInterPaletteMaxError: 3, gifInterPaletteMaxError: 3,
gifKeepDuplicateFrames: false,
gifReuse: true, gifReuse: true,
gifProgressive: false, gifProgressive: false,
tiffQuality: 80, tiffQuality: 80,

138
lib/index.d.ts vendored
View File

@ -730,20 +730,6 @@ declare namespace sharp {
*/ */
withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp; withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
/**
* Keep all XMP metadata from the input image in the output image.
* @returns A sharp instance that can be used to chain operations
*/
keepXmp(): Sharp;
/**
* Set XMP metadata in the output image.
* @param {string} xmp - String containing XMP metadata to be embedded in the output image.
* @returns A sharp instance that can be used to chain operations
* @throws {Error} Invalid parameters
*/
withXmp(xmp: string): Sharp;
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space. * The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
@ -1017,22 +1003,12 @@ declare namespace sharp {
pages?: number | undefined; pages?: number | undefined;
/** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */ /** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */
page?: number | undefined; page?: number | undefined;
/** TIFF specific input options */ /** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default -1) */
tiff?: TiffInputOptions | undefined;
/** SVG specific input options */
svg?: SvgInputOptions | undefined;
/** PDF specific input options */
pdf?: PdfInputOptions | undefined;
/** OpenSlide specific input options */
openSlide?: OpenSlideInputOptions | undefined;
/** JPEG 2000 specific input options */
jp2?: Jp2InputOptions | undefined;
/** Deprecated: use tiff.subifd instead */
subifd?: number | undefined; subifd?: number | undefined;
/** Deprecated: use pdf.background instead */ /** Level to extract from a multi-level input (OpenSlide), zero based. (optional, default 0) */
pdfBackground?: Colour | Color | undefined;
/** Deprecated: use openSlide.level instead */
level?: number | undefined; level?: number | undefined;
/** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */
pdfBackground?: Colour | Color | 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;
/** Describes raw pixel input image data. See raw() for pixel ordering. */ /** Describes raw pixel input image data. See raw() for pixel ordering. */
@ -1075,8 +1051,6 @@ declare namespace sharp {
interface CreateRaw extends Raw { interface CreateRaw extends Raw {
/** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */ /** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */
premultiplied?: boolean | undefined; premultiplied?: boolean | undefined;
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
pageHeight?: number | undefined;
} }
type CreateChannels = 3 | 4; type CreateChannels = 3 | 4;
@ -1092,9 +1066,6 @@ declare namespace sharp {
background: Colour | Color; background: Colour | Color;
/** Describes a noise to be created. */ /** Describes a noise to be created. */
noise?: Noise | undefined; noise?: Noise | undefined;
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
pageHeight?: number | undefined;
} }
interface CreateText { interface CreateText {
@ -1143,33 +1114,6 @@ declare namespace sharp {
valign?: VerticalAlignment | undefined; valign?: VerticalAlignment | undefined;
} }
interface TiffInputOptions {
/** Sub Image File Directory to extract, defaults to main image. Use -1 for all subifds. */
subifd?: number | undefined;
}
interface SvgInputOptions {
/** Custom CSS for SVG input, applied with a User Origin during the CSS cascade. */
stylesheet?: string | undefined;
/** Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. */
highBitdepth?: boolean | undefined;
}
interface PdfInputOptions {
/** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */
background?: Colour | Color | undefined;
}
interface OpenSlideInputOptions {
/** Level to extract from a multi-level input, zero based. (optional, default 0) */
level?: number | undefined;
}
interface Jp2InputOptions {
/** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */
oneshot?: boolean | undefined;
}
interface ExifDir { interface ExifDir {
[k: string]: string; [k: string]: string;
} }
@ -1202,13 +1146,13 @@ declare namespace sharp {
/** Number value of the EXIF Orientation header, if present */ /** Number value of the EXIF Orientation header, if present */
orientation?: number | undefined; orientation?: number | undefined;
/** Name of decoder used to decompress image data e.g. jpeg, png, webp, gif, svg */ /** Name of decoder used to decompress image data e.g. jpeg, png, webp, gif, svg */
format: keyof FormatEnum; format?: keyof FormatEnum | undefined;
/** Total size of image in bytes, for Stream and Buffer input only */ /** Total size of image in bytes, for Stream and Buffer input only */
size?: number | undefined; size?: number | undefined;
/** Number of pixels wide (EXIF orientation is not taken into consideration) */ /** Number of pixels wide (EXIF orientation is not taken into consideration) */
width: number; width?: number | undefined;
/** Number of pixels high (EXIF orientation is not taken into consideration) */ /** Number of pixels high (EXIF orientation is not taken into consideration) */
height: number; height?: number | undefined;
/** Any changed metadata after the image orientation is applied. */ /** Any changed metadata after the image orientation is applied. */
autoOrient: { autoOrient: {
/** Number of pixels wide (EXIF orientation is taken into consideration) */ /** Number of pixels wide (EXIF orientation is taken into consideration) */
@ -1217,19 +1161,19 @@ declare namespace sharp {
height: number; height: number;
}; };
/** Name of colour space interpretation */ /** Name of colour space interpretation */
space: keyof ColourspaceEnum; space?: keyof ColourspaceEnum | undefined;
/** Number of bands e.g. 3 for sRGB, 4 for CMYK */ /** Number of bands e.g. 3 for sRGB, 4 for CMYK */
channels: Channels; channels?: Channels | undefined;
/** Name of pixel depth format e.g. uchar, char, ushort, float ... */ /** Name of pixel depth format e.g. uchar, char, ushort, float ... */
depth: keyof DepthEnum; depth?: string | undefined;
/** Number of pixels per inch (DPI), if present */ /** Number of pixels per inch (DPI), if present */
density?: number | undefined; density?: number | undefined;
/** String containing JPEG chroma subsampling, 4:2:0 or 4:4:4 for RGB, 4:2:0:4 or 4:4:4:4 for CMYK */ /** String containing JPEG chroma subsampling, 4:2:0 or 4:4:4 for RGB, 4:2:0:4 or 4:4:4:4 for CMYK */
chromaSubsampling?: string | undefined; chromaSubsampling?: string | undefined;
/** Boolean indicating whether the image is interlaced using a progressive scan */ /** Boolean indicating whether the image is interlaced using a progressive scan */
isProgressive: boolean; isProgressive?: boolean | undefined;
/** Boolean indicating whether the image is palette-based (GIF, PNG). */ /** Boolean indicating whether the image is palette-based (GIF, PNG). */
isPalette: boolean; isPalette?: boolean | undefined;
/** Number of bits per sample for each channel (GIF, PNG). */ /** Number of bits per sample for each channel (GIF, PNG). */
bitsPerSample?: number | undefined; bitsPerSample?: number | undefined;
/** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */ /** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */
@ -1243,9 +1187,9 @@ declare namespace sharp {
/** Number of the primary page in a HEIF image */ /** Number of the primary page in a HEIF image */
pagePrimary?: number | undefined; pagePrimary?: number | undefined;
/** Boolean indicating the presence of an embedded ICC profile */ /** Boolean indicating the presence of an embedded ICC profile */
hasProfile: boolean; hasProfile?: boolean | undefined;
/** Boolean indicating the presence of an alpha transparency channel */ /** Boolean indicating the presence of an alpha transparency channel */
hasAlpha: boolean; hasAlpha?: boolean | undefined;
/** Buffer containing raw EXIF data, if present */ /** Buffer containing raw EXIF data, if present */
exif?: Buffer | undefined; exif?: Buffer | undefined;
/** Buffer containing raw ICC profile data, if present */ /** Buffer containing raw ICC profile data, if present */
@ -1254,8 +1198,6 @@ declare namespace sharp {
iptc?: Buffer | undefined; iptc?: Buffer | undefined;
/** Buffer containing raw XMP data, if present */ /** Buffer containing raw XMP data, if present */
xmp?: Buffer | undefined; xmp?: Buffer | undefined;
/** String containing XMP data, if valid UTF-8 */
xmpAsString?: string | undefined;
/** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */ /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */
tifftagPhotoshop?: Buffer | undefined; tifftagPhotoshop?: Buffer | undefined;
/** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */ /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */
@ -1394,8 +1336,6 @@ declare namespace sharp {
nearLossless?: boolean | undefined; nearLossless?: boolean | undefined;
/** Use high quality chroma subsampling (optional, default false) */ /** Use high quality chroma subsampling (optional, default false) */
smartSubsample?: boolean | undefined; smartSubsample?: boolean | undefined;
/** Auto-adjust the deblocking filter, slow but can improve low contrast edges (optional, default false) */
smartDeblock?: boolean | undefined;
/** 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) */
@ -1448,11 +1388,9 @@ declare namespace sharp {
/** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */ /** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */
dither?: number | undefined; dither?: number | undefined;
/** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */ /** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */
interFrameMaxError?: number | undefined; interFrameMaxError?: number;
/** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */ /** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */
interPaletteMaxError?: number | undefined; interPaletteMaxError?: number;
/** Keep duplicate frames in the output instead of combining them (optional, default false) */
keepDuplicateFrames?: boolean | undefined;
} }
interface TiffOptions extends OutputOptions { interface TiffOptions extends OutputOptions {
@ -1570,7 +1508,7 @@ declare namespace sharp {
interface Noise { interface Noise {
/** type of generated noise, currently only gaussian is supported. */ /** type of generated noise, currently only gaussian is supported. */
type: 'gaussian'; type?: 'gaussian' | undefined;
/** mean of pixels in generated noise. */ /** mean of pixels in generated noise. */
mean?: number | undefined; mean?: number | undefined;
/** standard deviation of pixels in generated noise. */ /** standard deviation of pixels in generated noise. */
@ -1761,10 +1699,6 @@ declare namespace sharp {
/** When using the attention crop strategy, the focal point of the cropped region */ /** When using the attention crop strategy, the focal point of the cropped region */
attentionX?: number | undefined; attentionX?: number | undefined;
attentionY?: number | undefined; attentionY?: number | undefined;
/** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */
pages?: number | undefined;
/** Number of pixels high each page in a multi-page image will be. */
pageHeight?: number | undefined;
} }
interface AvailableFormatInfo { interface AvailableFormatInfo {
@ -1787,8 +1721,6 @@ declare namespace sharp {
mitchell: 'mitchell'; mitchell: 'mitchell';
lanczos2: 'lanczos2'; lanczos2: 'lanczos2';
lanczos3: 'lanczos3'; lanczos3: 'lanczos3';
mks2013: 'mks2013';
mks2021: 'mks2021';
} }
interface PresetEnum { interface PresetEnum {
@ -1807,38 +1739,11 @@ declare namespace sharp {
} }
interface ColourspaceEnum { interface ColourspaceEnum {
'b-w': string;
cmc: string;
cmyk: string;
fourier: string;
grey16: string;
histogram: string;
hsv: string;
lab: string;
labq: string;
labs: string;
lch: string;
matrix: string;
multiband: string; multiband: string;
rgb: string; 'b-w': string;
rgb16: string; bw: string;
scrgb: string; cmyk: string;
srgb: string; srgb: string;
xyz: string;
yxy: string;
}
interface DepthEnum {
char: string;
complex: string;
double: string;
dpcomplex: string;
float: string;
int: string;
short: string;
uchar: string;
uint: string;
ushort: string;
} }
type FailOnOptions = 'none' | 'truncated' | 'error' | 'warning'; type FailOnOptions = 'none' | 'truncated' | 'error' | 'warning';
@ -1906,9 +1811,7 @@ declare namespace sharp {
interface FormatEnum { interface FormatEnum {
avif: AvailableFormatInfo; avif: AvailableFormatInfo;
dcraw: AvailableFormatInfo;
dz: AvailableFormatInfo; dz: AvailableFormatInfo;
exr: AvailableFormatInfo;
fits: AvailableFormatInfo; fits: AvailableFormatInfo;
gif: AvailableFormatInfo; gif: AvailableFormatInfo;
heif: AvailableFormatInfo; heif: AvailableFormatInfo;
@ -1922,7 +1825,6 @@ declare namespace sharp {
pdf: AvailableFormatInfo; pdf: AvailableFormatInfo;
png: AvailableFormatInfo; png: AvailableFormatInfo;
ppm: AvailableFormatInfo; ppm: AvailableFormatInfo;
rad: AvailableFormatInfo;
raw: AvailableFormatInfo; raw: AvailableFormatInfo;
svg: AvailableFormatInfo; svg: AvailableFormatInfo;
tiff: AvailableFormatInfo; tiff: AvailableFormatInfo;

View File

@ -3,6 +3,7 @@
'use strict'; 'use strict';
const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('./sharp'); const sharp = require('./sharp');
@ -22,27 +23,14 @@ const align = {
high: 'high' high: 'high'
}; };
const inputStreamParameters = [
// Limits and error handling
'failOn', 'limitInputPixels', 'unlimited',
// Format-generic
'animated', 'autoOrient', 'density', 'ignoreIcc', 'page', 'pages', 'sequentialRead',
// Format-specific
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
// Deprecated
'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
];
/** /**
* Extract input options, if any, from an object. * Extract input options, if any, from an object.
* @private * @private
*/ */
function _inputOptionsFromObject (obj) { function _inputOptionsFromObject (obj) {
const params = inputStreamParameters const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } = obj;
.filter(p => is.defined(obj[p])) return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient].some(is.defined)
.map(p => ([p, obj[p]])); ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient }
return params.length
? Object.fromEntries(params)
: undefined; : undefined;
} }
@ -185,6 +173,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
inputDescriptor.rawHeight = inputOptions.raw.height; inputDescriptor.rawHeight = inputOptions.raw.height;
inputDescriptor.rawChannels = inputOptions.raw.channels; inputDescriptor.rawChannels = inputOptions.raw.channels;
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
switch (input.constructor) { switch (input.constructor) {
case Uint8Array: case Uint8Array:
case Uint8ClampedArray: case Uint8ClampedArray:
@ -218,25 +208,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else { } else {
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }
inputDescriptor.rawPremultiplied = false;
if (is.defined(inputOptions.raw.premultiplied)) {
if (is.bool(inputOptions.raw.premultiplied)) {
inputDescriptor.rawPremultiplied = inputOptions.raw.premultiplied;
} else {
throw is.invalidParameterError('raw.premultiplied', 'boolean', inputOptions.raw.premultiplied);
}
}
inputDescriptor.rawPageHeight = 0;
if (is.defined(inputOptions.raw.pageHeight)) {
if (is.integer(inputOptions.raw.pageHeight) && inputOptions.raw.pageHeight > 0 && inputOptions.raw.pageHeight <= inputOptions.raw.height) {
if (inputOptions.raw.height % inputOptions.raw.pageHeight !== 0) {
throw new Error(`Expected raw.height ${inputOptions.raw.height} to be a multiple of raw.pageHeight ${inputOptions.raw.pageHeight}`);
}
inputDescriptor.rawPageHeight = inputOptions.raw.pageHeight;
} else {
throw is.invalidParameterError('raw.pageHeight', 'positive integer', inputOptions.raw.pageHeight);
}
}
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) { if (is.defined(inputOptions.animated)) {
@ -260,67 +231,25 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
} }
} }
// OpenSlide specific options // Multi-level input (OpenSlide)
if (is.object(inputOptions.openSlide) && is.defined(inputOptions.openSlide.level)) { if (is.defined(inputOptions.level)) {
if (is.integer(inputOptions.openSlide.level) && is.inRange(inputOptions.openSlide.level, 0, 256)) {
inputDescriptor.openSlideLevel = inputOptions.openSlide.level;
} else {
throw is.invalidParameterError('openSlide.level', 'integer between 0 and 256', inputOptions.openSlide.level);
}
} else if (is.defined(inputOptions.level)) {
// Deprecated
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) { if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
inputDescriptor.openSlideLevel = inputOptions.level; inputDescriptor.level = inputOptions.level;
} else { } else {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level); throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
} }
} }
// TIFF specific options // Sub Image File Directory (TIFF)
if (is.object(inputOptions.tiff) && is.defined(inputOptions.tiff.subifd)) { if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.tiff.subifd) && is.inRange(inputOptions.tiff.subifd, -1, 100000)) {
inputDescriptor.tiffSubifd = inputOptions.tiff.subifd;
} else {
throw is.invalidParameterError('tiff.subifd', 'integer between -1 and 100000', inputOptions.tiff.subifd);
}
} else if (is.defined(inputOptions.subifd)) {
// Deprecated
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) { if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.tiffSubifd = inputOptions.subifd; inputDescriptor.subifd = inputOptions.subifd;
} else { } else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd); throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
} }
} }
// SVG specific options // PDF background colour
if (is.object(inputOptions.svg)) { if (is.defined(inputOptions.pdfBackground)) {
if (is.defined(inputOptions.svg.stylesheet)) { this._setBackgroundColourOption('pdfBackground', inputOptions.pdfBackground);
if (is.string(inputOptions.svg.stylesheet)) {
inputDescriptor.svgStylesheet = inputOptions.svg.stylesheet;
} else {
throw is.invalidParameterError('svg.stylesheet', 'string', inputOptions.svg.stylesheet);
}
}
if (is.defined(inputOptions.svg.highBitdepth)) {
if (is.bool(inputOptions.svg.highBitdepth)) {
inputDescriptor.svgHighBitdepth = inputOptions.svg.highBitdepth;
} else {
throw is.invalidParameterError('svg.highBitdepth', 'boolean', inputOptions.svg.highBitdepth);
}
}
}
// PDF specific options
if (is.object(inputOptions.pdf) && is.defined(inputOptions.pdf.background)) {
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdf.background);
} else if (is.defined(inputOptions.pdfBackground)) {
// Deprecated
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground);
}
// JPEG 2000 specific options
if (is.object(inputOptions.jp2) && is.defined(inputOptions.jp2.oneshot)) {
if (is.bool(inputOptions.jp2.oneshot)) {
inputDescriptor.jp2Oneshot = inputOptions.jp2.oneshot;
} else {
throw is.invalidParameterError('jp2.oneshot', 'boolean', inputOptions.jp2.oneshot);
}
} }
// Create new image // Create new image
if (is.defined(inputOptions.create)) { if (is.defined(inputOptions.create)) {
@ -333,50 +262,39 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.createWidth = inputOptions.create.width; inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height; inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels; inputDescriptor.createChannels = inputOptions.create.channels;
inputDescriptor.createPageHeight = 0;
if (is.defined(inputOptions.create.pageHeight)) {
if (is.integer(inputOptions.create.pageHeight) && inputOptions.create.pageHeight > 0 && inputOptions.create.pageHeight <= inputOptions.create.height) {
if (inputOptions.create.height % inputOptions.create.pageHeight !== 0) {
throw new Error(`Expected create.height ${inputOptions.create.height} to be a multiple of create.pageHeight ${inputOptions.create.pageHeight}`);
}
inputDescriptor.createPageHeight = inputOptions.create.pageHeight;
} else {
throw is.invalidParameterError('create.pageHeight', 'positive integer', inputOptions.create.pageHeight);
}
}
// Noise // Noise
if (is.defined(inputOptions.create.noise)) { if (is.defined(inputOptions.create.noise)) {
if (!is.object(inputOptions.create.noise)) { if (!is.object(inputOptions.create.noise)) {
throw new Error('Expected noise to be an object'); throw new Error('Expected noise to be an object');
} }
if (inputOptions.create.noise.type !== 'gaussian') { if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
throw new Error('Only gaussian noise is supported at the moment'); throw new Error('Only gaussian noise is supported at the moment');
} }
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (!is.inRange(inputOptions.create.channels, 1, 4)) { if (!is.inRange(inputOptions.create.channels, 1, 4)) {
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels); throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
} }
inputDescriptor.createNoiseMean = 128; inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (is.defined(inputOptions.create.noise.mean)) {
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) { if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean; inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
} else { } else {
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean); throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
} }
}
inputDescriptor.createNoiseSigma = 30;
if (is.defined(inputOptions.create.noise.sigma)) {
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) { if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma; inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
} else { } else {
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma); throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
} }
}
} else if (is.defined(inputOptions.create.background)) { } else if (is.defined(inputOptions.create.background)) {
if (!is.inRange(inputOptions.create.channels, 3, 4)) { if (!is.inRange(inputOptions.create.channels, 3, 4)) {
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels); throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
} }
inputDescriptor.createBackground = this._getBackgroundColourOption(inputOptions.create.background); const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
} else { } else {
throw new Error('Expected valid noise or background to create a new input image'); throw new Error('Expected valid noise or background to create a new input image');
} }
@ -492,7 +410,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
if (is.defined(inputOptions.join.background)) { if (is.defined(inputOptions.join.background)) {
inputDescriptor.joinBackground = this._getBackgroundColourOption(inputOptions.join.background); const background = color(inputOptions.join.background);
inputDescriptor.joinBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
} }
if (is.defined(inputOptions.join.halign)) { if (is.defined(inputOptions.join.halign)) {
if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) { if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
@ -605,7 +529,6 @@ function _isStreamInput () {
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present * - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present * - `xmp`: Buffer containing raw XMP data, if present
* - `xmpAsString`: String containing XMP data, if valid UTF-8.
* - `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.

View File

@ -18,9 +18,9 @@ const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).versio
const prebuiltPlatforms = [ const prebuiltPlatforms = [
'darwin-arm64', 'darwin-x64', 'darwin-arm64', 'darwin-x64',
'linux-arm', 'linux-arm64', 'linux-ppc64', 'linux-s390x', 'linux-x64', 'linux-arm', 'linux-arm64', 'linux-s390x', 'linux-x64',
'linuxmusl-arm64', 'linuxmusl-x64', 'linuxmusl-arm64', 'linuxmusl-x64',
'win32-arm64', 'win32-ia32', 'win32-x64' 'win32-ia32', 'win32-x64'
]; ];
const spawnSyncOptions = { const spawnSyncOptions = {

View File

@ -3,6 +3,7 @@
'use strict'; 'use strict';
const color = require('color');
const is = require('./is'); const is = require('./is');
/** /**
@ -66,7 +67,13 @@ function rotate (angle, options) {
} else if (is.number(angle)) { } else if (is.number(angle)) {
this.options.rotationAngle = angle; this.options.rotationAngle = angle;
if (is.object(options) && options.background) { if (is.object(options) && options.background) {
this._setBackgroundColourOption('rotationBackground', options.background); const backgroundColour = color(options.background);
this.options.rotationBackground = [
backgroundColour.red(),
backgroundColour.green(),
backgroundColour.blue(),
Math.round(backgroundColour.alpha() * 255)
];
} }
} else { } else {
throw is.invalidParameterError('angle', 'numeric', angle); throw is.invalidParameterError('angle', 'numeric', angle);

View File

@ -312,59 +312,6 @@ function withIccProfile (icc, options) {
return this; return this;
} }
/**
* Keep XMP metadata from the input image in the output image.
*
* @since 0.34.3
*
* @example
* const outputWithXmp = await sharp(inputWithXmp)
* .keepXmp()
* .toBuffer();
*
* @returns {Sharp}
*/
function keepXmp () {
this.options.keepMetadata |= 0b00010;
return this;
}
/**
* Set XMP metadata in the output image.
*
* Supported by PNG, JPEG, WebP, and TIFF output.
*
* @since 0.34.3
*
* @example
* const xmpString = `
* <?xml version="1.0"?>
* <x:xmpmeta xmlns:x="adobe:ns:meta/">
* <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
* <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
* <dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
* </rdf:Description>
* </rdf:RDF>
* </x:xmpmeta>`;
*
* const data = await sharp(input)
* .withXmp(xmpString)
* .toBuffer();
*
* @param {string} xmp String containing XMP metadata to be embedded in the output image.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function withXmp (xmp) {
if (is.string(xmp) && xmp.length > 0) {
this.options.withXmp = xmp;
this.options.keepMetadata |= 0b00010;
} else {
throw is.invalidParameterError('xmp', 'non-empty string', xmp);
}
return this;
}
/** /**
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image. * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
* *
@ -782,7 +729,6 @@ function webp (options) {
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
* @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32 * @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
* @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256 * @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
* @param {boolean} [options.keepDuplicateFrames=false] - keep duplicate frames in the output instead of combining them
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds) * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
@ -833,13 +779,6 @@ function gif (options) {
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError); throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
} }
} }
if (is.defined(options.keepDuplicateFrames)) {
if (is.bool(options.keepDuplicateFrames)) {
this._setBooleanOption('gifKeepDuplicateFrames', options.keepDuplicateFrames);
} else {
throw is.invalidParameterError('keepDuplicateFrames', 'boolean', options.keepDuplicateFrames);
}
}
} }
trySetAnimationOptions(options, this.options); trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options); return this._updateFormatOut('gif', options);
@ -1080,9 +1019,6 @@ 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
* and requires a CPU with ARM64v8.4 or later.
*
* @example * @example
* const data = await sharp(input) * const data = await sharp(input)
* .avif({ effort: 2 }) * .avif({ effort: 2 })
@ -1629,8 +1565,6 @@ module.exports = function (Sharp) {
withExifMerge, withExifMerge,
keepIccProfile, keepIccProfile,
withIccProfile, withIccProfile,
keepXmp,
withXmp,
keepMetadata, keepMetadata,
withMetadata, withMetadata,
toFormat, toFormat,

View File

@ -129,7 +129,7 @@ function isResizeExpected (options) {
* *
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property. * Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
* *
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="/api-resize-fit.svg"> * <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
* *
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are: * When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. * - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
@ -150,8 +150,6 @@ function isResizeExpected (options) {
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf). * - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
* - `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
* - `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
* *
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators. * When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
* Downsampling kernels without a matching upsampling interpolator map to `cubic`. * Downsampling kernels without a matching upsampling interpolator map to `cubic`.

View File

@ -135,9 +135,15 @@ cache(true);
* e.g. libaom manages its own 4 threads when encoding AVIF images, * e.g. libaom manages its own 4 threads when encoding AVIF images,
* and these are independent of the value set here. * and these are independent of the value set here.
* *
* :::note * The maximum number of images that sharp can process in parallel
* Further {@link /performance|control over performance} is available. * is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
* ::: * which defaults to 4.
*
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
*
* For example, by default, a machine with 8 CPU cores will process
* 4 images in parallel and use up to 8 threads per image,
* so there will be up to 32 concurrent threads.
* *
* @example * @example
* const threads = sharp.concurrency(); // 4 * const threads = sharp.concurrency(); // 4

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-arm64", "name": "@img/sharp-darwin-arm64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with macOS 64-bit ARM", "description": "Prebuilt sharp for use with macOS 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.0" "@img/sharp-libvips-darwin-arm64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-x64", "name": "@img/sharp-darwin-x64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with macOS x64", "description": "Prebuilt sharp for use with macOS x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.0" "@img/sharp-libvips-darwin-x64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -0,0 +1,73 @@
// Copyright 2013 Lovell Fuller and others.
// SPDX-License-Identifier: Apache-2.0
'use strict';
// Populate contents of all packages with the current GitHub release
const { readFile, writeFile, appendFile, copyFile, rm } = require('node:fs/promises');
const path = require('node:path');
const { Readable } = require('node:stream');
const { pipeline } = require('node:stream/promises');
const { createGunzip } = require('node:zlib');
const { extract } = require('tar-fs');
const { workspaces } = require('./package.json');
const { version } = require('../package.json');
const mapTarballEntry = (header) => {
header.name = path.basename(header.name);
return header;
};
const licensing = `
## Licensing
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
`;
workspaces.map(async platform => {
const prebuildPlatform = platform === 'wasm32' ? 'emscripten-wasm32' : platform;
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${prebuildPlatform}.tar.gz`;
const dir = path.join(__dirname, platform);
const response = await fetch(url);
if (!response.ok) {
console.log(`Skipping ${platform}: ${response.statusText}`);
return;
}
// Extract prebuild tarball
const lib = path.join(dir, 'lib');
await rm(lib, { force: true, recursive: true });
await pipeline(
Readable.fromWeb(response.body),
createGunzip(),
extract(lib, { map: mapTarballEntry })
);
// Generate README
const { name, description } = require(`./${platform}/package.json`);
await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
// Copy Apache-2.0 LICENSE
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm)
if (platform.startsWith('win') || platform.startsWith('wasm')) {
const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform;
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..');
// Copy versions.json
await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json'));
// Append third party licensing to README
const readme = await readFile(path.join(sharpLibvipsDir, 'README.md'), { encoding: 'utf-8' });
const thirdParty = readme.substring(readme.indexOf('\nThis software contains'));
appendFile(path.join(dir, 'README.md'), thirdParty);
}
});

View File

@ -3,62 +3,24 @@
'use strict'; 'use strict';
// Populate the npm package for the current platform with the local build // Populate contents of a single npm/sharpen-sharp-<build-platform> package
// with the local/CI build directory for local/CI prebuild testing
const { copyFileSync, cpSync, readFileSync, writeFileSync, appendFileSync } = require('node:fs'); const fs = require('node:fs');
const { basename, join } = require('node:path'); const path = require('node:path');
const { buildPlatformArch } = require('../lib/libvips'); const { buildPlatformArch } = require('../lib/libvips');
const licensing = `
## Licensing
Copyright 2013 Lovell Fuller and others.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
`;
const platform = buildPlatformArch(); const platform = buildPlatformArch();
const destDir = join(__dirname, platform); const dest = path.join(__dirname, platform);
console.log(`Populating npm package for platform: ${platform}`);
// Copy binaries // Use same config as prebuild to copy binary files
const releaseDir = join(__dirname, '..', 'src', 'build', 'Release'); const release = path.join(__dirname, '..', 'src', 'build', 'Release');
const libDir = join(destDir, 'lib'); const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8'));
cpSync(releaseDir, libDir, { const include = new RegExp(prebuildrc['include-regex'], 'i');
fs.cpSync(release, path.join(dest, 'lib'), {
recursive: true, recursive: true,
filter: (file) => { filter: (file) => {
const name = basename(file); const name = path.basename(file);
return name === 'Release' || return name === 'Release' || include.test(name);
(name.startsWith('sharp-') && name.includes('.node')) ||
(name.startsWith('libvips-') && name.endsWith('.dll'));
} }
}); });
// Generate README
const { name, description } = require(`./${platform}/package.json`);
writeFileSync(join(destDir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
// Copy Apache-2.0 LICENSE
copyFileSync(join(__dirname, '..', 'LICENSE'), join(destDir, 'LICENSE'));
// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm)
if (platform.startsWith('win') || platform.startsWith('wasm')) {
const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform;
const sharpLibvipsDir = join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..');
// Copy versions.json
copyFileSync(join(sharpLibvipsDir, 'versions.json'), join(destDir, 'versions.json'));
// Append third party licensing to README
const readme = readFileSync(join(sharpLibvipsDir, 'README.md'), { encoding: 'utf-8' });
const thirdParty = readme.substring(readme.indexOf('\nThis software contains'));
appendFileSync(join(destDir, 'README.md'), thirdParty);
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm", "name": "@img/sharp-linux-arm",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)", "description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.0" "@img/sharp-libvips-linux-arm": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm64", "name": "@img/sharp-linux-arm64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM", "description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.0" "@img/sharp-libvips-linux-arm64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-ppc64", "name": "@img/sharp-linux-ppc64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"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,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.0" "@img/sharp-libvips-linux-ppc64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-s390x", "name": "@img/sharp-linux-s390x",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (glibc) s390x", "description": "Prebuilt sharp for use with Linux (glibc) s390x",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.0" "@img/sharp-libvips-linux-s390x": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-x64", "name": "@img/sharp-linux-x64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (glibc) x64", "description": "Prebuilt sharp for use with Linux (glibc) x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.0" "@img/sharp-libvips-linux-x64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-arm64", "name": "@img/sharp-linuxmusl-arm64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM", "description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0" "@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-x64", "name": "@img/sharp-linuxmusl-x64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Linux (musl) x64", "description": "Prebuilt sharp for use with Linux (musl) x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.0" "@img/sharp-libvips-linuxmusl-x64": "1.1.0"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp", "name": "@img/sharp",
"version": "0.34.3-rc.0", "version": "0.34.1",
"private": "true", "private": "true",
"workspaces": [ "workspaces": [
"darwin-arm64", "darwin-arm64",
@ -13,7 +13,6 @@
"linuxmusl-arm64", "linuxmusl-arm64",
"linuxmusl-x64", "linuxmusl-x64",
"wasm32", "wasm32",
"win32-arm64",
"win32-ia32", "win32-ia32",
"win32-x64" "win32-x64"
] ]

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-wasm32", "name": "@img/sharp-wasm32",
"version": "0.34.3-rc.0", "version": "0.34.1",
"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",
@ -31,7 +31,7 @@
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
}, },
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.4.4" "@emnapi/runtime": "^1.4.0"
}, },
"cpu": [ "cpu": [
"wasm32" "wasm32"

View File

@ -1,39 +0,0 @@
{
"name": "@img/sharp-win32-arm64",
"version": "0.34.3-rc.0",
"description": "Prebuilt sharp for use with Windows 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"repository": {
"type": "git",
"url": "git+https://github.com/lovell/sharp.git",
"directory": "npm/win32-arm64"
},
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"funding": {
"url": "https://opencollective.com/libvips"
},
"preferUnplugged": true,
"files": [
"lib",
"versions.json"
],
"publishConfig": {
"access": "public"
},
"type": "commonjs",
"exports": {
"./sharp.node": "./lib/sharp-win32-arm64.node",
"./package": "./package.json",
"./versions": "./versions.json"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"os": [
"win32"
],
"cpu": [
"arm64"
]
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-ia32", "name": "@img/sharp-win32-ia32",
"version": "0.34.3-rc.0", "version": "0.34.1",
"description": "Prebuilt sharp for use with Windows x86 (32-bit)", "description": "Prebuilt sharp for use with Windows x86 (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",

View File

@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-x64", "name": "@img/sharp-win32-x64",
"version": "0.34.3-rc.0", "version": "0.34.1",
"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",

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.3-rc.0", "version": "0.34.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
"contributors": [ "contributors": [
@ -92,7 +92,7 @@
"Don Denton <don@happycollision.com>" "Don Denton <don@happycollision.com>"
], ],
"scripts": { "scripts": {
"install": "node install/check.js", "install": "node install/check",
"clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types", "test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types",
"test-lint": "semistandard && cpplint", "test-lint": "semistandard && cpplint",
@ -100,7 +100,8 @@
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"", "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"test-types": "tsd", "test-types": "tsd",
"package-from-local-build": "node npm/from-local-build.js", "package-from-local-build": "node npm/from-local-build",
"package-from-github-release": "node npm/from-github-release",
"docs-build": "node docs/build.mjs", "docs-build": "node docs/build.mjs",
"docs-serve": "cd docs && npm start", "docs-serve": "cd docs && npm start",
"docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" "docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
@ -137,66 +138,68 @@
], ],
"dependencies": { "dependencies": {
"color": "^4.2.3", "color": "^4.2.3",
"detect-libc": "^2.0.4", "detect-libc": "^2.0.3",
"semver": "^7.7.2" "semver": "^7.7.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.3-rc.0", "@img/sharp-darwin-arm64": "0.34.1",
"@img/sharp-darwin-x64": "0.34.3-rc.0", "@img/sharp-darwin-x64": "0.34.1",
"@img/sharp-libvips-darwin-arm64": "1.2.0", "@img/sharp-libvips-darwin-arm64": "1.1.0",
"@img/sharp-libvips-darwin-x64": "1.2.0", "@img/sharp-libvips-darwin-x64": "1.1.0",
"@img/sharp-libvips-linux-arm": "1.2.0", "@img/sharp-libvips-linux-arm": "1.1.0",
"@img/sharp-libvips-linux-arm64": "1.2.0", "@img/sharp-libvips-linux-arm64": "1.1.0",
"@img/sharp-libvips-linux-ppc64": "1.2.0", "@img/sharp-libvips-linux-ppc64": "1.1.0",
"@img/sharp-libvips-linux-s390x": "1.2.0", "@img/sharp-libvips-linux-s390x": "1.1.0",
"@img/sharp-libvips-linux-x64": "1.2.0", "@img/sharp-libvips-linux-x64": "1.1.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
"@img/sharp-libvips-linuxmusl-x64": "1.2.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0",
"@img/sharp-linux-arm": "0.34.3-rc.0", "@img/sharp-linux-arm": "0.34.1",
"@img/sharp-linux-arm64": "0.34.3-rc.0", "@img/sharp-linux-arm64": "0.34.1",
"@img/sharp-linux-ppc64": "0.34.3-rc.0", "@img/sharp-linux-s390x": "0.34.1",
"@img/sharp-linux-s390x": "0.34.3-rc.0", "@img/sharp-linux-x64": "0.34.1",
"@img/sharp-linux-x64": "0.34.3-rc.0", "@img/sharp-linuxmusl-arm64": "0.34.1",
"@img/sharp-linuxmusl-arm64": "0.34.3-rc.0", "@img/sharp-linuxmusl-x64": "0.34.1",
"@img/sharp-linuxmusl-x64": "0.34.3-rc.0", "@img/sharp-wasm32": "0.34.1",
"@img/sharp-wasm32": "0.34.3-rc.0", "@img/sharp-win32-ia32": "0.34.1",
"@img/sharp-win32-arm64": "0.34.3-rc.0", "@img/sharp-win32-x64": "0.34.1"
"@img/sharp-win32-ia32": "0.34.3-rc.0",
"@img/sharp-win32-x64": "0.34.3-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@emnapi/runtime": "^1.4.4", "@emnapi/runtime": "^1.4.0",
"@img/sharp-libvips-dev": "1.2.0", "@img/sharp-libvips-dev": "1.1.0",
"@img/sharp-libvips-dev-wasm32": "1.2.0", "@img/sharp-libvips-dev-wasm32": "1.1.0",
"@img/sharp-libvips-win32-arm64": "1.2.0", "@img/sharp-libvips-win32-ia32": "1.1.0",
"@img/sharp-libvips-win32-ia32": "1.2.0", "@img/sharp-libvips-win32-x64": "1.1.0",
"@img/sharp-libvips-win32-x64": "1.2.0",
"@types/node": "*", "@types/node": "*",
"cc": "^3.0.1", "cc": "^3.0.1",
"emnapi": "^1.4.4", "emnapi": "^1.4.0",
"exif-reader": "^2.0.2", "exif-reader": "^2.0.2",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"icc": "^3.0.0", "icc": "^3.0.0",
"jsdoc-to-markdown": "^9.1.1", "jsdoc-to-markdown": "^9.1.1",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^11.7.1", "mocha": "^11.1.0",
"node-addon-api": "^8.4.0", "node-addon-api": "^8.3.1",
"node-gyp": "^11.2.0",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"prebuild": "^13.0.1",
"semistandard": "^17.0.0", "semistandard": "^17.0.0",
"tar-fs": "^3.1.0", "tar-fs": "^3.0.8",
"tsd": "^0.32.0" "tsd": "^0.31.2"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
}, },
"config": { "config": {
"libvips": ">=8.17.1" "libvips": ">=8.16.1"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
9
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@ -163,8 +163,6 @@
}, },
'xcode_settings': { 'xcode_settings': {
'OTHER_LDFLAGS': [ 'OTHER_LDFLAGS': [
'-Wl,-s',
'-Wl,-dead_strip',
# Ensure runtime linking is relative to sharp.node # Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'', '-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'',
'-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'', '-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
@ -178,9 +176,6 @@
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=1' '_GLIBCXX_USE_CXX11_ABI=1'
], ],
'cflags_cc': [
'<!(node -p "require(\'detect-libc\').isNonGlibcLinuxSync() ? \'\' : \'-flto=auto\'")'
],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [
'-l:libvips-cpp.so.<(vips_version)' '-l:libvips-cpp.so.<(vips_version)'

View File

@ -93,7 +93,6 @@ namespace sharp {
descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied"); descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
descriptor->rawPageHeight = AttrAsUint32(input, "rawPageHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
@ -102,35 +101,23 @@ namespace sharp {
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrAsUint32(input, "page"); descriptor->page = AttrAsUint32(input, "page");
} }
// SVG
if (HasAttr(input, "svgStylesheet")) {
descriptor->svgStylesheet = AttrAsStr(input, "svgStylesheet");
}
if (HasAttr(input, "svgHighBitdepth")) {
descriptor->svgHighBitdepth = AttrAsBool(input, "svgHighBitdepth");
}
// Multi-level input (OpenSlide) // Multi-level input (OpenSlide)
if (HasAttr(input, "openSlideLevel")) { if (HasAttr(input, "level")) {
descriptor->openSlideLevel = AttrAsUint32(input, "openSlideLevel"); descriptor->level = AttrAsUint32(input, "level");
} }
// subIFD (OME-TIFF) // subIFD (OME-TIFF)
if (HasAttr(input, "subifd")) { if (HasAttr(input, "subifd")) {
descriptor->tiffSubifd = AttrAsInt32(input, "tiffSubifd"); descriptor->subifd = AttrAsInt32(input, "subifd");
} }
// // PDF background color // // PDF background color
if (HasAttr(input, "pdfBackground")) { if (HasAttr(input, "pdfBackground")) {
descriptor->pdfBackground = AttrAsVectorOfDouble(input, "pdfBackground"); descriptor->pdfBackground = AttrAsVectorOfDouble(input, "pdfBackground");
} }
// Use JPEG 2000 oneshot mode?
if (HasAttr(input, "jp2Oneshot")) {
descriptor->jp2Oneshot = AttrAsBool(input, "jp2Oneshot");
}
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createPageHeight = AttrAsUint32(input, "createPageHeight");
if (HasAttr(input, "createNoiseType")) { if (HasAttr(input, "createNoiseType")) {
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType"); descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean"); descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
@ -284,7 +271,6 @@ namespace sharp {
case ImageType::EXR: id = "exr"; break; case ImageType::EXR: id = "exr"; break;
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::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;
@ -333,8 +319,6 @@ namespace sharp {
{ "VipsForeignLoadJxlBuffer", ImageType::JXL }, { "VipsForeignLoadJxlBuffer", ImageType::JXL },
{ "VipsForeignLoadRadFile", ImageType::RAD }, { "VipsForeignLoadRadFile", ImageType::RAD },
{ "VipsForeignLoadRadBuffer", ImageType::RAD }, { "VipsForeignLoadRadBuffer", ImageType::RAD },
{ "VipsForeignLoadDcRawFile", ImageType::DCRAW },
{ "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
{ "VipsForeignLoadVips", ImageType::VIPS }, { "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS }, { "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW } { "VipsForeignLoadRaw", ImageType::RAW }
@ -399,48 +383,6 @@ namespace sharp {
imageType == ImageType::HEIF; imageType == ImageType::HEIF;
} }
/*
Format-specific options builder
*/
vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor) {
vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
switch (imageType) {
case ImageType::SVG:
option->set("dpi", descriptor->density)
->set("stylesheet", descriptor->svgStylesheet.data())
->set("high_bitdepth", descriptor->svgHighBitdepth);
break;
case ImageType::TIFF:
option->set("tiffSubifd", descriptor->tiffSubifd);
break;
case ImageType::PDF:
option->set("dpi", descriptor->density)
->set("background", descriptor->pdfBackground);
break;
case ImageType::OPENSLIDE:
option->set("openSlideLevel", descriptor->openSlideLevel);
break;
case ImageType::JP2:
option->set("oneshot", descriptor->jp2Oneshot);
break;
case ImageType::MAGICK:
option->set("density", std::to_string(descriptor->density).data());
break;
default:
break;
}
return option;
}
/* /*
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)
*/ */
@ -458,10 +400,6 @@ namespace sharp {
} else { } else {
image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16; image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16;
} }
if (descriptor->rawPageHeight > 0) {
image.set(VIPS_META_PAGE_HEIGHT, descriptor->rawPageHeight);
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->rawHeight / descriptor->rawPageHeight));
}
if (descriptor->rawPremultiplied) { if (descriptor->rawPremultiplied) {
image = image.unpremultiply(); image = image.unpremultiply();
} }
@ -471,7 +409,31 @@ namespace sharp {
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength); imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = GetOptionsForImageType(imageType, descriptor); vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
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);
@ -511,10 +473,6 @@ namespace sharp {
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB)) channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
.new_from_image(background); .new_from_image(background);
} }
if (descriptor->createPageHeight > 0) {
image.set(VIPS_META_PAGE_HEIGHT, descriptor->createPageHeight);
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->createHeight / descriptor->createPageHeight));
}
image = image.cast(VIPS_FORMAT_UCHAR); image = image.cast(VIPS_FORMAT_UCHAR);
imageType = ImageType::RAW; imageType = ImageType::RAW;
} else if (descriptor->textValue.length() > 0) { } else if (descriptor->textValue.length() > 0) {
@ -558,7 +516,31 @@ namespace sharp {
} }
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = GetOptionsForImageType(imageType, descriptor); vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
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);
@ -669,21 +651,22 @@ namespace sharp {
*/ */
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) { VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = !delay.empty(); bool hasDelay = !delay.empty();
// Avoid a copy if none of the animation properties are needed.
if (nPages == 1 && !hasDelay && loop == -1) return image;
if (delay.size() == 1) {
// We have just one delay, repeat that value for all frames.
delay.insert(delay.end(), nPages - 1, delay[0]);
}
// Attaching metadata, need to copy the image.
VImage copy = image.copy(); VImage copy = image.copy();
// Only set page-height if we have more than one page, or this could // Only set page-height if we have more than one page, or this could
// accidentally turn into an animated image later. // accidentally turn into an animated image later.
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight); if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) { if (hasDelay) copy.set("delay", delay);
if (delay.size() == 1) {
// We have just one delay, repeat that value for all frames.
delay.insert(delay.end(), nPages - 1, delay[0]);
}
copy.set("delay", delay);
}
if (nPages == 1 && !hasDelay && loop == -1) {
loop = 1;
}
if (loop != -1) copy.set("loop", loop); if (loop != -1) copy.set("loop", loop);
return copy; return copy;
@ -969,6 +952,14 @@ namespace sharp {
return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
} }
/*
Return the image alpha maximum. Useful for combining alpha bands. scRGB
images are 0 - 1 for image data, but the alpha is 0 - 255.
*/
double MaximumImageAlpha(VipsInterpretation const interpretation) {
return Is16Bit(interpretation) ? 65535.0 : 255.0;
}
/* /*
Convert RGBA value to another colourspace Convert RGBA value to another colourspace
*/ */
@ -1011,16 +1002,16 @@ namespace sharp {
0.0722 * colour[2]) 0.0722 * colour[2])
}; };
} }
// Add alpha channel(s) to alphaColour colour // Add alpha channel to alphaColour colour
if (colour[3] < 255.0 || image.has_alpha()) { if (colour[3] < 255.0 || image.has_alpha()) {
int extraBands = image.bands() > 4 ? image.bands() - 3 : 1; alphaColour.push_back(colour[3] * multiplier);
alphaColour.insert(alphaColour.end(), extraBands, colour[3] * multiplier);
} }
// Ensure alphaColour colour uses correct colourspace // Ensure alphaColour colour uses correct colourspace
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply); alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
// Add non-transparent alpha channel, if required // Add non-transparent alpha channel, if required
if (colour[3] < 255.0 && !image.has_alpha()) { if (colour[3] < 255.0 && !image.has_alpha()) {
image = image.bandjoin_const({ 255 * multiplier }); image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
} }
return std::make_tuple(image, alphaColour); return std::make_tuple(image, alphaColour);
} }
@ -1040,7 +1031,9 @@ namespace sharp {
*/ */
VImage EnsureAlpha(VImage image, double const value) { VImage EnsureAlpha(VImage image, double const value) {
if (!image.has_alpha()) { if (!image.has_alpha()) {
image = image.bandjoin_const({ value * vips_interpretation_max_alpha(image.interpretation()) }); std::vector<double> alpha;
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
} }
return image; return image;
} }

View File

@ -15,9 +15,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 < 16) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 1) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 16 && VIPS_MICRO_VERSION < 1)
#error "libvips version 8.17.1+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.16.1+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if defined(__has_include) #if defined(__has_include)
@ -48,13 +48,13 @@ namespace sharp {
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
bool rawPremultiplied; bool rawPremultiplied;
int rawPageHeight;
int pages; int pages;
int page; int page;
int level;
int subifd;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
int createPageHeight;
std::vector<double> createBackground; std::vector<double> createBackground;
std::string createNoiseType; std::string createNoiseType;
double createNoiseMean; double createNoiseMean;
@ -77,12 +77,7 @@ namespace sharp {
std::vector<double> joinBackground; std::vector<double> joinBackground;
VipsAlign joinHalign; VipsAlign joinHalign;
VipsAlign joinValign; VipsAlign joinValign;
std::string svgStylesheet;
bool svgHighBitdepth;
int tiffSubifd;
int openSlideLevel;
std::vector<double> pdfBackground; std::vector<double> pdfBackground;
bool jp2Oneshot;
InputDescriptor(): InputDescriptor():
autoOrient(false), autoOrient(false),
@ -100,13 +95,13 @@ namespace sharp {
rawWidth(0), rawWidth(0),
rawHeight(0), rawHeight(0),
rawPremultiplied(false), rawPremultiplied(false),
rawPageHeight(0),
pages(1), pages(1),
page(0), page(0),
level(0),
subifd(-1),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
createPageHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 }, createBackground{ 0.0, 0.0, 0.0, 255.0 },
createNoiseMean(0.0), createNoiseMean(0.0),
createNoiseSigma(0.0), createNoiseSigma(0.0),
@ -125,11 +120,7 @@ namespace sharp {
joinBackground{ 0.0, 0.0, 0.0, 255.0 }, joinBackground{ 0.0, 0.0, 0.0, 255.0 },
joinHalign(VIPS_ALIGN_LOW), joinHalign(VIPS_ALIGN_LOW),
joinValign(VIPS_ALIGN_LOW), joinValign(VIPS_ALIGN_LOW),
svgHighBitdepth(false), pdfBackground{ 255.0, 255.0, 255.0, 255.0 } {}
tiffSubifd(-1),
openSlideLevel(0),
pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
jp2Oneshot(false) {}
}; };
// Convenience methods to access the attributes of a Napi::Object // Convenience methods to access the attributes of a Napi::Object
@ -169,7 +160,6 @@ namespace sharp {
EXR, EXR,
JXL, JXL,
RAD, RAD,
DCRAW,
VIPS, VIPS,
RAW, RAW,
UNKNOWN, UNKNOWN,
@ -226,9 +216,14 @@ namespace sharp {
ImageType DetermineImageType(char const *file); ImageType DetermineImageType(char const *file);
/* /*
Format-specific options builder Does this image type support multiple pages?
*/ */
vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor); bool ImageTypeSupportsPage(ImageType imageType);
/*
Does this image type support removal of safety limits?
*/
bool ImageTypeSupportsUnlimited(ImageType imageType);
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
@ -362,6 +357,12 @@ namespace sharp {
*/ */
bool Is16Bit(VipsInterpretation const interpretation); bool Is16Bit(VipsInterpretation const interpretation);
/*
Return the image alpha maximum. Useful for combining alpha bands. scRGB
images are 0 - 1 for image data, but the alpha is 0 - 255.
*/
double MaximumImageAlpha(VipsInterpretation const interpretation);
/* /*
Convert RGBA value to another colourspace Convert RGBA value to another colourspace
*/ */

View File

@ -262,10 +262,6 @@ class MetadataWorker : public Napi::AsyncWorker {
} }
if (baton->xmpLength > 0) { if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
if (g_utf8_validate(static_cast<char const *>(baton->xmp), baton->xmpLength, nullptr)) {
info.Set("xmpAsString",
Napi::String::New(env, static_cast<char const *>(baton->xmp), baton->xmpLength));
}
} }
if (baton->tifftagPhotoshopLength > 0) { if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop", info.Set("tifftagPhotoshop",

View File

@ -241,7 +241,11 @@ class PipelineWorker : public Napi::AsyncWorker {
// factor for jpegload*, a double scale factor for webpload*, // factor for jpegload*, a double scale factor for webpload*,
// pdfload* and svgload* // pdfload* and svgload*
if (jpegShrinkOnLoad > 1) { if (jpegShrinkOnLoad > 1) {
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad); vips::VOption *option = VImage::option()
->set("access", access)
->set("shrink", jpegShrinkOnLoad)
->set("unlimited", baton->input->unlimited)
->set("fail_on", baton->input->failOn);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload JPEG buffer // Reload JPEG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -252,8 +256,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} }
} else if (scale != 1.0) { } else if (scale != 1.0) {
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale); vips::VOption *option = VImage::option()
->set("access", access)
->set("scale", scale)
->set("fail_on", baton->input->failOn);
if (inputImageType == sharp::ImageType::WEBP) { if (inputImageType == sharp::ImageType::WEBP) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload WebP buffer // Reload WebP buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -264,6 +274,9 @@ class PipelineWorker : public Napi::AsyncWorker {
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option); image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
} }
} else if (inputImageType == sharp::ImageType::SVG) { } else if (inputImageType == sharp::ImageType::SVG) {
option->set("unlimited", baton->input->unlimited);
option->set("dpi", baton->input->density);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload SVG buffer // Reload SVG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -278,6 +291,10 @@ class PipelineWorker : public Napi::AsyncWorker {
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled"); throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
} }
} else if (inputImageType == sharp::ImageType::PDF) { } else if (inputImageType == sharp::ImageType::PDF) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
option->set("dpi", baton->input->density);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload PDF buffer // Reload PDF buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -287,6 +304,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Reload PDF file // Reload PDF file
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option); image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
} }
sharp::SetDensity(image, baton->input->density); sharp::SetDensity(image, baton->input->density);
} }
} else { } else {
@ -650,6 +668,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
composite->input->access = access; composite->input->access = access;
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline);
if (composite->input->autoOrient) { if (composite->input->autoOrient) {
// Respect EXIF Orientation // Respect EXIF Orientation
@ -714,7 +733,8 @@ class PipelineWorker : public Napi::AsyncWorker {
// gravity was used for extract_area, set it back to its default value of 0 // gravity was used for extract_area, set it back to its default value of 0
composite->gravity = 0; composite->gravity = 0;
} }
// Ensure image to composite is with unpremultiplied alpha // Ensure image to composite is sRGB with unpremultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
compositeImage = sharp::EnsureAlpha(compositeImage, 1); compositeImage = sharp::EnsureAlpha(compositeImage, 1);
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply(); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position // Calculate position
@ -739,12 +759,7 @@ class PipelineWorker : public Napi::AsyncWorker {
xs.push_back(left); xs.push_back(left);
ys.push_back(top); ys.push_back(top);
} }
image = VImage::composite(images, modes, VImage::option() image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST
? VIPS_INTERPRETATION_sRGB
: baton->colourspacePipeline)
->set("x", xs)
->set("y", ys));
image = sharp::RemoveGifPalette(image); image = sharp::RemoveGifPalette(image);
} }
@ -876,12 +891,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image.set(s.first.data(), s.second.data()); image.set(s.first.data(), s.second.data());
} }
} }
// XMP buffer
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
image = image.copy();
image.set(VIPS_META_XMP_NAME, nullptr,
const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
}
// Number of channels used in output image // Number of channels used in output image
baton->channels = image.bands(); baton->channels = image.bands();
baton->width = image.width(); baton->width = image.width();
@ -992,7 +1002,6 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("interlace", baton->gifProgressive) ->set("interlace", baton->gifProgressive)
->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interframe_maxerror", baton->gifInterFrameMaxError)
->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
->set("dither", baton->gifDither))); ->set("dither", baton->gifDither)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@ -1196,9 +1205,6 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reuse", baton->gifReuse) ->set("reuse", baton->gifReuse)
->set("interlace", baton->gifProgressive) ->set("interlace", baton->gifProgressive)
->set("interframe_maxerror", baton->gifInterFrameMaxError)
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
->set("dither", baton->gifDither)); ->set("dither", baton->gifDither));
baton->formatOut = "gif"; baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@ -1353,8 +1359,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// 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)) {
try { try {
uint32_t const size = static_cast<uint32_t>( uint32_t const size = static_cast<uint32_t>(std::filesystem::file_size(baton->fileOut));
std::filesystem::file_size(std::filesystem::u8path(baton->fileOut)));
info.Set("size", size); info.Set("size", size);
} catch (...) {} } catch (...) {}
} }
@ -1711,7 +1716,6 @@ 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->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");
@ -1752,7 +1756,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");

View File

@ -169,7 +169,6 @@ struct PipelineBaton {
double gifDither; double gifDither;
double gifInterFrameMaxError; double gifInterFrameMaxError;
double gifInterPaletteMaxError; double gifInterPaletteMaxError;
bool gifKeepDuplicateFrames;
bool gifReuse; bool gifReuse;
bool gifProgressive; bool gifProgressive;
int tiffQuality; int tiffQuality;
@ -202,7 +201,6 @@ struct PipelineBaton {
std::string withIccProfile; std::string withIccProfile;
std::unordered_map<std::string, std::string> withExif; std::unordered_map<std::string, std::string> withExif;
bool withExifMerge; bool withExifMerge;
std::string withXmp;
int timeoutSeconds; int timeoutSeconds;
std::vector<double> convKernel; std::vector<double> convKernel;
int convKernelWidth; int convKernelWidth;
@ -344,7 +342,6 @@ struct PipelineBaton {
gifDither(1.0), gifDither(1.0),
gifInterFrameMaxError(0.0), gifInterFrameMaxError(0.0),
gifInterPaletteMaxError(3.0), gifInterPaletteMaxError(3.0),
gifKeepDuplicateFrames(false),
gifReuse(true), gifReuse(true),
gifProgressive(false), gifProgressive(false),
tiffQuality(80), tiffQuality(80),
@ -387,7 +384,7 @@ struct PipelineBaton {
ensureAlpha(-1.0), ensureAlpha(-1.0),
colourspacePipeline(VIPS_INTERPRETATION_LAST), colourspacePipeline(VIPS_INTERPRETATION_LAST),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
loop(-1), loop(1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@ -18,10 +18,8 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
vips_init("sharp"); vips_init("sharp");
}); });
for (auto domain : { "VIPS", "vips2tiff" }) { g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
g_log_set_handler(domain, static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
}
// Methods available to JavaScript // Methods available to JavaScript
exports.Set("metadata", Napi::Function::New(env, metadata)); exports.Set("metadata", Napi::Function::New(env, metadata));

View File

@ -60,7 +60,7 @@ class StatsWorker : public Napi::AsyncWorker {
// Image is not opaque when alpha layer is present and contains a non-mamixa value // Image is not opaque when alpha layer is present and contains a non-mamixa value
if (image.has_alpha()) { if (image.has_alpha()) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != vips_interpretation_max_alpha(image.interpretation())) { if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false; baton->isOpaque = false;
} }
} }

View File

@ -119,7 +119,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
Napi::Object format = Napi::Object::New(env); Napi::Object format = Napi::Object::New(env);
for (std::string const f : { for (std::string const f : {
"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"
}) { }) {
// Input // Input
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str()); const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());

View File

@ -1,11 +1,11 @@
FROM ubuntu:25.04 FROM ubuntu:24.10
ARG BRANCH=main ARG BRANCH=main
# Install basic dependencies # Install basic dependencies
RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg
# Install latest Node.js LTS # Install latest Node.js LTS
RUN curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs RUN apt-get install -y nodejs

View File

@ -552,32 +552,6 @@ async.series({
} }
}); });
} }
}).add('sharp-mks2013', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2013' })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-mks2021', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2021' })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).on('cycle', function (event) { }).on('cycle', function (event) {
console.log('kernels ' + String(event.target)); console.log('kernels ' + String(event.target));
}).on('complete', function () { }).on('complete', function () {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

View File

@ -117,7 +117,6 @@ module.exports = {
inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045 inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2 inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
inputJp2TileParts: getPath('relax_tileparts.jp2'), // kdu_expand -i relax.jp2 -o relax-tmp.tif ; kdu_compress -i relax-tmp.tif -o relax_tileparts.jp2 -jp2_space sRGB Clayers=8 -rate 1.0,0.04 Stiles='{128,128}' ORGtparts=L ; rm relax-tmp.tif
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif

Binary file not shown.

View File

@ -188,8 +188,6 @@ sharp(input)
// of the image data in inputBuffer // of the image data in inputBuffer
}); });
sharp(input).resize({ kernel: 'mks2013' });
transformer = sharp() transformer = sharp()
.resize(200, 200, { .resize(200, 200, {
fit: 'cover', fit: 'cover',
@ -375,8 +373,6 @@ sharp(input)
.gif({ reuse: false }) .gif({ reuse: false })
.gif({ progressive: true }) .gif({ progressive: true })
.gif({ progressive: false }) .gif({ progressive: false })
.gif({ keepDuplicateFrames: true })
.gif({ keepDuplicateFrames: false })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
console.log(data); console.log(data);
@ -418,7 +414,6 @@ sharp({
channels: 4, channels: 4,
height: 25000, height: 25000,
width: 25000, width: 25000,
pageHeight: 1000,
}, },
limitInputPixels: false, limitInputPixels: false,
}) })
@ -436,6 +431,9 @@ sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile
// Support `unlimited` input option // Support `unlimited` input option
sharp('input.png', { unlimited: true }).resize(320, 240).toFile('outfile.png'); sharp('input.png', { unlimited: true }).resize(320, 240).toFile('outfile.png');
// Support `subifd` input option for tiffs
sharp('input.tiff', { subifd: 3 }).resize(320, 240).toFile('outfile.png');
// Support creating with noise // Support creating with noise
sharp({ sharp({
create: { create: {
@ -692,8 +690,6 @@ sharp(input)
k2: 'v2' k2: 'v2'
} }
}) })
.keepXmp()
.withXmp('test')
.keepIccProfile() .keepIccProfile()
.withIccProfile('filename') .withIccProfile('filename')
.withIccProfile('filename', { attach: false }); .withIccProfile('filename', { attach: false });
@ -720,29 +716,10 @@ sharp(input).composite([
} }
]) ])
// Support format-specific input options
const colour: sharp.Colour = '#fff'; const colour: sharp.Colour = '#fff';
const color: sharp.Color = '#fff'; const color: sharp.Color = '#fff';
sharp({ pdf: { background: colour } }); sharp({ pdfBackground: colour });
sharp({ pdf: { background: color } }); sharp({ pdfBackground: color });
sharp({ pdfBackground: colour }); // Deprecated
sharp({ pdfBackground: color }); // Deprecated
sharp({ tiff: { subifd: 3 } });
sharp({ subifd: 3 }); // Deprecated
sharp({ openSlide: { level: 0 } });
sharp({ level: 0 }); // Deprecated
sharp({ jp2: { oneshot: true } });
sharp({ jp2: { oneshot: false } });
sharp({ svg: { stylesheet: 'test' }});
sharp({ svg: { highBitdepth: true }});
sharp({ svg: { highBitdepth: false }});
// Raw input options
const raw: sharp.Raw = { width: 1, height: 1, channels: 3 };
sharp({ raw });
sharp({ raw: { ...raw, premultiplied: true } });
sharp({ raw: { ...raw, premultiplied: false } });
sharp({ raw: { ...raw, pageHeight: 1 } });
sharp({ autoOrient: true }); sharp({ autoOrient: true });
sharp({ autoOrient: false }); sharp({ autoOrient: false });

View File

@ -122,26 +122,6 @@ describe('composite', () => {
}); });
}); });
it('scrgb pipeline', () => {
const filename = 'composite-red-scrgb.png';
const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename);
return sharp({
create: {
width: 32, height: 32, channels: 4, background: red
}
})
.pipelineColourspace('scrgb')
.composite([{
input: fixtures.inputPngWithTransparency16bit,
blend: 'color-burn'
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('multiple', async () => { it('multiple', async () => {
const filename = 'composite-multiple.png'; const filename = 'composite-multiple.png';
const actual = fixtures.path(`output.${filename}`); const actual = fixtures.path(`output.${filename}`);

View File

@ -187,17 +187,6 @@ describe('GIF input', () => {
); );
}); });
it('invalid keepDuplicateFrames throws', () => {
assert.throws(
() => sharp().gif({ keepDuplicateFrames: -1 }),
/Expected boolean for keepDuplicateFrames but received -1 of type number/
);
assert.throws(
() => sharp().gif({ keepDuplicateFrames: 'fail' }),
/Expected boolean for keepDuplicateFrames but received fail of type string/
);
});
it('should work with streams when only animated is set', function (done) { it('should work with streams when only animated is set', function (done) {
fs.createReadStream(fixtures.inputGifAnimated) fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true })) .pipe(sharp({ animated: true }))
@ -236,20 +225,6 @@ describe('GIF input', () => {
assert.strict(before.length > after.length); assert.strict(before.length > after.length);
}); });
it('should keep duplicate frames via keepDuplicateFrames', async () => {
const create = { width: 8, height: 8, channels: 4, background: 'blue' };
const input = sharp([{ create }, { create }], { join: { animated: true } });
const before = await input.gif({ keepDuplicateFrames: false }).toBuffer();
const after = await input.gif({ keepDuplicateFrames: true }).toBuffer();
assert.strict(before.length < after.length);
const beforeMeta = await sharp(before).metadata();
const afterMeta = await sharp(after).metadata();
assert.strictEqual(beforeMeta.pages, 1);
assert.strictEqual(afterMeta.pages, 2);
});
it('non-animated input defaults to no-loop', async () => { it('non-animated input defaults to no-loop', async () => {
for (const input of [fixtures.inputGif, fixtures.inputPng]) { for (const input of [fixtures.inputGif, fixtures.inputPng]) {
const data = await sharp(input) const data = await sharp(input)
@ -263,15 +238,4 @@ describe('GIF input', () => {
assert.strictEqual(1, loop); assert.strictEqual(1, loop);
} }
}); });
it('Animated GIF to animated WebP merges identical frames', async () => {
const webp = await sharp(fixtures.inputGifAnimated, { animated: true })
.webp()
.toBuffer();
const { delay, loop, pages } = await sharp(webp).metadata();
assert.deepStrictEqual([120, 120, 90, 120, 120, 90, 120, 90, 30], delay);
assert.strictEqual(0, loop);
assert.strictEqual(9, pages);
});
}); });

View File

@ -867,91 +867,52 @@ describe('Input/output', function () {
sharp({ pages: '1' }); sharp({ pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/); }, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
}); });
it('Valid openSlide.level property', function () { it('Valid level property', function () {
sharp({ openSlide: { level: 1 } });
sharp({ level: 1 }); sharp({ level: 1 });
}); });
it('Invalid openSlide.level property (string) throws', function () { it('Invalid level property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ openSlide: { level: '1' } }), sharp({ level: '1' });
/Expected integer between 0 and 256 for openSlide.level but received 1 of type string/ }, /Expected integer between 0 and 256 for level but received 1 of type string/);
);
assert.throws(
() => sharp({ level: '1' }),
/Expected integer between 0 and 256 for level but received 1 of type string/
);
}); });
it('Invalid openSlide.level property (negative) throws', function () { it('Invalid level property (negative) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ openSlide: { level: -1 } }), sharp({ level: -1 });
/Expected integer between 0 and 256 for openSlide\.level but received -1 of type number/ }, /Expected integer between 0 and 256 for level but received -1 of type number/);
);
assert.throws(
() => sharp({ level: -1 }),
/Expected integer between 0 and 256 for level but received -1 of type number/
);
}); });
it('Valid tiff.subifd property', function () { it('Valid subifd property', function () {
sharp({ tiff: { subifd: 1 } });
sharp({ subifd: 1 }); sharp({ subifd: 1 });
}); });
it('Invalid tiff.subifd property (string) throws', function () { it('Invalid subifd property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ tiff: { subifd: '1' } }), sharp({ subifd: '1' });
/Expected integer between -1 and 100000 for tiff\.subifd but received 1 of type string/ }, /Expected integer between -1 and 100000 for subifd but received 1 of type string/);
);
assert.throws(
() => sharp({ subifd: '1' }),
/Expected integer between -1 and 100000 for subifd but received 1 of type string/
);
}); });
it('Invalid tiff.subifd property (float) throws', function () { it('Invalid subifd property (float) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ tiff: { subifd: 1.2 } }), sharp({ subifd: 1.2 });
/Expected integer between -1 and 100000 for tiff\.subifd but received 1.2 of type number/ }, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/);
);
assert.throws(
() => sharp({ subifd: 1.2 }),
/Expected integer between -1 and 100000 for subifd but received 1.2 of type number/
);
}); });
it('Valid pdf.background property (string)', function () { it('Valid pdfBackground property (string)', function () {
sharp({ pdf: { background: '#00ff00' } });
sharp({ pdfBackground: '#00ff00' }); sharp({ pdfBackground: '#00ff00' });
}); });
it('Valid pdf.background property (object)', function () { it('Valid pdfBackground property (object)', function () {
sharp({ pdf: { background: { r: 0, g: 255, b: 0 } } });
sharp({ pdfBackground: { r: 0, g: 255, b: 0 } }); sharp({ pdfBackground: { r: 0, g: 255, b: 0 } });
}); });
it('Invalid pdf.background property (string) throws', function () { it('Invalid pdfBackground property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: '00ff00' } }), sharp({ pdfBackground: '00ff00' });
/Unable to parse color from string/ }, /Unable to parse color from string/);
);
assert.throws(
() => sharp({ pdfBackground: '00ff00' }),
/Unable to parse color from string/
);
}); });
it('Invalid pdf.background property (number) throws', function () { it('Invalid pdfBackground property (number) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: 255 } }), sharp({ pdfBackground: 255 });
/Expected object or string for background/ }, /Expected object or string for background/);
);
assert.throws(
() => sharp({ pdf: { background: 255 } }),
/Expected object or string for background/
);
}); });
it('Invalid pdf.background property (object)', function () { it('Invalid pdfBackground property (object)', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: { red: 0, green: 255, blue: 0 } } }), sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } });
/Unable to parse color from object/ }, /Unable to parse color from object/);
);
assert.throws(
() => sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } }),
/Unable to parse color from object/
);
}); });
}); });
@ -1075,21 +1036,4 @@ describe('Input/output', function () {
}); });
readable.pipe(inPipeline).pipe(badPipeline); readable.pipe(inPipeline).pipe(badPipeline);
}); });
it('supports wide-character filenames', async () => {
const filename = fixtures.path('output.图片.jpg');
const create = {
width: 8,
height: 8,
channels: 3,
background: 'green'
};
await sharp({ create }).toFile(filename);
const { width, height, channels, format } = await sharp(filename).metadata();
assert.strictEqual(width, 8);
assert.strictEqual(height, 8);
assert.strictEqual(channels, 3);
assert.strictEqual(format, 'jpeg');
});
}); });

View File

@ -93,38 +93,10 @@ describe('JP2 output', () => {
}); });
}); });
it('can use the jp2Oneshot option to handle multi-part tiled JPEG 2000 file', async () => { it('Invalid JP2 chromaSubsampling value throws error', function () {
const outputJpg = fixtures.path('output.jpg'); assert.throws(function () {
await assert.rejects( sharp().jpeg({ chromaSubsampling: '4:2:2' });
() => sharp(fixtures.inputJp2TileParts).toFile(outputJpg)
);
await assert.doesNotReject(async () => {
await sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true }).toFile(outputJpg);
const { format, width, height } = await sharp(outputJpg).metadata();
assert.strictEqual(format, 'jpeg');
assert.strictEqual(width, 320);
assert.strictEqual(height, 240);
}); });
}); });
it('Invalid JP2 chromaSubsampling value throws error', () => {
assert.throws(
() => sharp().jp2({ chromaSubsampling: '4:2:2' }),
/Expected one of 4:2:0, 4:4:4 but received 4:2:2 of type string/
);
});
} }
it('valid JP2 oneshot value does not throw error', () => {
assert.doesNotThrow(
() => sharp({ jp2: { oneshot: true } })
);
});
it('invalid JP2 oneshot value throws error', () => {
assert.throws(
() => sharp({ jp2: { oneshot: 'fail' } }),
/Expected boolean for jp2.oneshot but received fail of type string/
);
});
}); });

View File

@ -179,7 +179,7 @@ describe('libvips binaries', function () {
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, '30afd744f9'); assert.strictEqual(locatorHash, '9b2ea457de');
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

@ -82,7 +82,6 @@ describe('Image metadata', function () {
assert.strictEqual(true, metadata.xmp instanceof Buffer); assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(12466, metadata.xmp.byteLength); assert.strictEqual(12466, metadata.xmp.byteLength);
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0); assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
assert(metadata.xmpAsString.startsWith('<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'));
done(); done();
}); });
}); });
@ -107,8 +106,6 @@ describe('Image metadata', function () {
assert.strictEqual(3248, metadata.autoOrient.height); assert.strictEqual(3248, metadata.autoOrient.height);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
assert.strictEqual('undefined', typeof metadata.xmp);
assert.strictEqual('undefined', typeof metadata.xmpAsString);
assert.strictEqual('inch', metadata.resolutionUnit); assert.strictEqual('inch', metadata.resolutionUnit);
done(); done();
}); });
@ -1103,170 +1100,6 @@ describe('Image metadata', function () {
assert.strictEqual(exif2.Image.Software, 'sharp'); assert.strictEqual(exif2.Image.Software, 'sharp');
}); });
describe('XMP metadata tests', function () {
it('withMetadata preserves existing XMP metadata from input', async () => {
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.withMetadata()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.length > 0);
// Check that XMP starts with the expected XML declaration
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
});
it('keepXmp preserves existing XMP metadata from input', async () => {
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.keepXmp()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.length > 0);
// Check that XMP starts with the expected XML declaration
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
});
it('withXmp with custom XMP replaces existing XMP', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>Test Creator</rdf:li></rdf:Seq></dc:creator><dc:title><rdf:Alt><rdf:li xml:lang="x-default">Test Title</rdf:li></rdf:Alt></dc:title></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.withXmp(customXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
// Check that the XMP contains our custom content
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('Test Creator'));
assert.strictEqual(true, xmpString.includes('Test Title'));
});
it('withXmp with custom XMP buffer on image without existing XMP', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:description><rdf:Alt><rdf:li xml:lang="x-default">Added via Sharp</rdf:li></rdf:Alt></dc:description></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(320, 240)
.withXmp(customXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
// Check that the XMP contains our custom content
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('Added via Sharp'));
});
it('withXmp with valid XMP metadata for different image formats', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:subject><rdf:Bag><rdf:li>test</rdf:li><rdf:li>metadata</rdf:li></rdf:Bag></dc:subject></rdf:Description></rdf:RDF></x:xmpmeta>';
// Test with JPEG output
const jpegData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.jpeg()
.withXmp(customXmp)
.toBuffer();
const jpegMetadata = await sharp(jpegData).metadata();
assert.strictEqual('object', typeof jpegMetadata.xmp);
assert.strictEqual(true, jpegMetadata.xmp instanceof Buffer);
assert.strictEqual(true, jpegMetadata.xmp.toString().includes('test'));
// Test with PNG output (PNG should also support XMP metadata)
const pngData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.png()
.withXmp(customXmp)
.toBuffer();
const pngMetadata = await sharp(pngData).metadata();
// PNG format should preserve XMP metadata when using withXmp
assert.strictEqual('object', typeof pngMetadata.xmp);
assert.strictEqual(true, pngMetadata.xmp instanceof Buffer);
assert.strictEqual(true, pngMetadata.xmp.toString().includes('test'));
// Test with WebP output (WebP should also support XMP metadata)
const webpData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.webp()
.withXmp(customXmp)
.toBuffer();
const webpMetadata = await sharp(webpData).metadata();
// WebP format should preserve XMP metadata when using withXmp
assert.strictEqual('object', typeof webpMetadata.xmp);
assert.strictEqual(true, webpMetadata.xmp instanceof Buffer);
assert.strictEqual(true, webpMetadata.xmp.toString().includes('test'));
});
it('XMP metadata persists through multiple operations', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier>persistent-test</dc:identifier></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(320, 240)
.withXmp(customXmp)
.rotate(90)
.blur(1)
.sharpen()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.toString().includes('persistent-test'));
});
it('withXmp XMP works with WebP format specifically', async () => {
const webpXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>WebP Creator</rdf:li></rdf:Seq></dc:creator><dc:format>image/webp</dc:format></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(120, 80)
.webp({ quality: 80 })
.withXmp(webpXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('webp', metadata.format);
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('WebP Creator'));
assert.strictEqual(true, xmpString.includes('image/webp'));
});
it('withXmp XMP validation - non-string input', function () {
assert.throws(
() => sharp().withXmp(123),
/Expected non-empty string for xmp but received 123 of type number/
);
});
it('withXmp XMP validation - null input', function () {
assert.throws(
() => sharp().withXmp(null),
/Expected non-empty string for xmp but received null of type object/
);
});
it('withXmp XMP validation - empty string', function () {
assert.throws(
() => sharp().withXmp(''),
/Expected non-empty string for xmp/
);
});
});
describe('Invalid parameters', function () { describe('Invalid parameters', function () {
it('String orientation', function () { it('String orientation', function () {
assert.throws(function () { assert.throws(function () {

View File

@ -173,26 +173,6 @@ describe('Gaussian noise', function () {
}); });
}); });
it('animated noise', async () => {
const gif = await sharp({
create: {
width: 16,
height: 64,
pageHeight: 16,
channels: 3,
noise: { type: 'gaussian' }
}
})
.gif()
.toBuffer();
const { width, height, pages, delay } = await sharp(gif).metadata();
assert.strictEqual(width, 16);
assert.strictEqual(height, 16);
assert.strictEqual(pages, 4);
assert.strictEqual(delay.length, 4);
});
it('no create object properties specified', function () { it('no create object properties specified', function () {
assert.throws(function () { assert.throws(function () {
sharp({ sharp({
@ -279,29 +259,4 @@ describe('Gaussian noise', function () {
}); });
}); });
}); });
it('Invalid pageHeight', () => {
const create = {
width: 8,
height: 8,
channels: 4,
noise: { type: 'gaussian' }
};
assert.throws(
() => sharp({ create: { ...create, pageHeight: 'zoinks' } }),
/Expected positive integer for create\.pageHeight but received zoinks of type string/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: -1 } }),
/Expected positive integer for create\.pageHeight but received -1 of type number/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: 9 } }),
/Expected positive integer for create\.pageHeight but received 9 of type number/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: 3 } }),
/Expected create\.height 8 to be a multiple of create\.pageHeight 3/
);
});
}); });

View File

@ -55,35 +55,6 @@ describe('Raw pixel data', function () {
}); });
}); });
it('Invalid premultiplied', () => {
assert.throws(
() => sharp({ raw: { width: 1, height: 1, channels: 4, premultiplied: 'zoinks' } }),
/Expected boolean for raw\.premultiplied but received zoinks of type string/
);
});
it('Invalid pageHeight', () => {
const width = 8;
const height = 8;
const channels = 4;
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 'zoinks' } }),
/Expected positive integer for raw\.pageHeight but received zoinks of type string/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: -1 } }),
/Expected positive integer for raw\.pageHeight but received -1 of type number/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 9 } }),
/Expected positive integer for raw\.pageHeight but received 9 of type number/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 3 } }),
/Expected raw\.height 8 to be a multiple of raw\.pageHeight 3/
);
});
it('RGB', function (done) { it('RGB', function (done) {
// Convert to raw pixel data // Convert to raw pixel data
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
@ -314,23 +285,6 @@ describe('Raw pixel data', function () {
} }
}); });
it('Animated', async () => {
const gif = await sharp(
Buffer.alloc(8),
{ raw: { width: 1, height: 2, channels: 4, pageHeight: 1 }, animated: true }
)
.gif({ keepDuplicateFrames: true })
.toBuffer();
console.log(await sharp(gif).metadata());
const { width, height, pages, delay } = await sharp(gif).metadata();
assert.strictEqual(width, 1);
assert.strictEqual(height, 1);
assert.strictEqual(pages, 2);
assert.strictEqual(delay.length, 2);
});
describe('16-bit roundtrip', () => { describe('16-bit roundtrip', () => {
it('grey', async () => { it('grey', async () => {
const grey = 42000; const grey = 42000;

View File

@ -806,33 +806,4 @@ describe('Resize fit=contain', function () {
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/9-c.png'), data, done); fixtures.assertSimilar(fixtures.expected('./embedgravitybird/9-c.png'), data, done);
}); });
}); });
it('multiple alpha channels', async () => {
const create = {
width: 20,
height: 12,
channels: 4,
background: 'green'
};
const multipleAlphaChannels = await sharp({ create })
.joinChannel({ create })
.tiff({ compression: 'deflate' })
.toBuffer();
const data = await sharp(multipleAlphaChannels)
.resize({
width: 8,
height: 8,
fit: 'contain',
background: 'blue'
})
.tiff({ compression: 'deflate' })
.toBuffer();
const { format, width, height, space, channels } = await sharp(data).metadata();
assert.deepStrictEqual(format, 'tiff');
assert.deepStrictEqual(width, 8);
assert.deepStrictEqual(height, 8);
assert.deepStrictEqual(space, 'srgb');
assert.deepStrictEqual(channels, 8);
});
}); });

View File

@ -139,41 +139,6 @@ describe('SVG input', function () {
assert.strictEqual(info.channels, 4); assert.strictEqual(info.channels, 4);
}); });
it('Can apply custom CSS', async () => {
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="4" fill="green" />
</svg>`;
const stylesheet = 'circle { fill: red }';
const [r, g, b, a] = await sharp(Buffer.from(svg), { svg: { stylesheet } })
.extract({ left: 5, top: 5, width: 1, height: 1 })
.raw()
.toBuffer();
assert.deepEqual([r, g, b, a], [255, 0, 0, 255]);
});
it('Invalid stylesheet input option throws', () =>
assert.throws(
() => sharp({ svg: { stylesheet: 123 } }),
/Expected string for svg\.stylesheet but received 123 of type number/
)
);
it('Valid highBitdepth input option does not throw', () =>
assert.doesNotThrow(
() => sharp({ svg: { highBitdepth: true } })
)
);
it('Invalid highBitdepth input option throws', () =>
assert.throws(
() => sharp({ svg: { highBitdepth: 123 } }),
/Expected boolean for svg\.highBitdepth but received 123 of type number/
)
);
it('Fails to render SVG larger than 32767x32767', () => it('Fails to render SVG larger than 32767x32767', () =>
assert.rejects( assert.rejects(
() => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(), () => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(),

View File

@ -59,7 +59,7 @@ describe('Text to image', function () {
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
assert.ok(inRange(info.width, 400, 600), `Actual width ${info.width}`); assert.ok(inRange(info.width, 400, 600), `Actual width ${info.width}`);
assert.ok(inRange(info.height, 290, 500), `Actual height ${info.height}`); assert.ok(inRange(info.height, 300, 500), `Actual height ${info.height}`);
assert.ok(inRange(info.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`); assert.ok(inRange(info.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`);
done(); done();
}); });

View File

@ -10,20 +10,18 @@ const sharp = require('../../');
describe('Utilities', function () { describe('Utilities', function () {
describe('Cache', function () { describe('Cache', function () {
it('Can be disabled', function (done) { it('Can be disabled', function (done) {
const check = setInterval(() => { queueMicrotask(() => {
sharp.cache(false);
const cache = sharp.cache(false); const cache = sharp.cache(false);
const empty = assert.strictEqual(cache.memory.current, 0);
cache.memory.current + assert.strictEqual(cache.memory.max, 0);
cache.memory.max + assert.strictEqual(typeof cache.memory.high, 'number');
cache.files.current + assert.strictEqual(cache.files.current, 0);
cache.files.max + assert.strictEqual(cache.files.max, 0);
cache.items.current + assert.strictEqual(cache.items.current, 0);
cache.items.max === 0; assert.strictEqual(cache.items.max, 0);
if (empty) {
clearInterval(check);
done(); done();
} });
}, 2000);
}); });
it('Can be enabled with defaults', function () { it('Can be enabled with defaults', function () {
const cache = sharp.cache(true); const cache = sharp.cache(true);