diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index a29034945..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,105 +0,0 @@ -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" diff --git a/.cirrus.yml b/.cirrus.yml index a0b1785b0..5a6fb8c59 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,5 +1,5 @@ freebsd_instance: - image_family: freebsd-14-0-snap + image_family: freebsd-16-0-snap task: name: FreeBSD @@ -9,9 +9,10 @@ task: prerequisites_script: - pkg update -f - pkg upgrade -y - - pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm + - pkg install -y devel/git devel/pkgconf graphics/vips www/node22 www/npm - pkg-config --modversion vips-cpp install_script: - - npm install --build-from-source + - npm install + - npm run build test_script: - - npx mocha --no-config --spec=test/unit/io.js --timeout=30000 + - node --test test/unit/io.js diff --git a/.editorconfig b/.editorconfig index 5760be583..62fb51fed 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,4 @@ indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false +max_line_length = 120 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c806fc5e7..b65d48b98 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,8 +6,6 @@ Hello, thank you for your interest in helping! Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem. -If you're having installation problems, please include the output of running `npm install --verbose sharp`. - New bugs are assigned a `triage` label whilst under investigation. ## Submit a new feature request @@ -16,7 +14,7 @@ If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exist it's probably fastest to add a comment to it about your requirement. Implementation is usually straightforward if libvips -[already supports](https://www.libvips.org/API/current/func-list.html) +[already supports](https://www.libvips.org/API/current/function-list.html) the feature you need. ## Submit a Pull Request to fix a bug @@ -27,12 +25,11 @@ Please select the `main` branch as the destination for your Pull Request so your Please squash your changes into a single commit using a command like `git rebase -i upstream/main`. -To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`. +To test C++ changes, you can compile the module using `npm run build` and then run the tests using `npm test`. ## Submit a Pull Request with a new feature Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature. -A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory. Please also update the [TypeScript definitions](https://github.com/lovell/sharp/tree/main/lib/index.d.ts), along with the [type definition tests](https://github.com/lovell/sharp/tree/main/test/types/sharp.test-d.ts). Where possible, the functional tests use gradient-based perceptual hashes diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a94483a33..cd6838590 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,24 @@ on: - pull_request permissions: {} jobs: - github-runner: + lint: permissions: - contents: write - name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }} + contents: read + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v5 + with: + node-version: "24" + - run: npm install --ignore-scripts + - run: npm run lint-cpp + - run: npm run lint-js + - run: npm run lint-types + build-native: + permissions: + contents: read + needs: lint + name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" runs-on: ${{ matrix.os }} container: ${{ matrix.container }} strategy: @@ -20,7 +34,7 @@ jobs: nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: linux-x64 - prebuild: true + package: true - os: ubuntu-24.04 container: rockylinux:8 nodejs_arch: x64 @@ -37,7 +51,7 @@ jobs: container: node:18-alpine3.17 nodejs_version_major: 18 platform: linuxmusl-x64 - prebuild: true + package: true - os: ubuntu-24.04 container: node:20-alpine3.18 nodejs_version_major: 20 @@ -46,76 +60,100 @@ jobs: container: node:22-alpine3.20 nodejs_version_major: 22 platform: linuxmusl-x64 - - os: macos-13 + - 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-15-intel nodejs_arch: x64 nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: darwin-x64 - prebuild: true - - os: macos-13 + package: true + - os: macos-15-intel nodejs_arch: x64 nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: darwin-x64 - - os: macos-13 + - os: macos-15-intel nodejs_arch: x64 nodejs_version: "^22.9.0" nodejs_version_major: 22 platform: darwin-x64 - - os: macos-14 + - os: macos-15 nodejs_arch: arm64 nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: darwin-arm64 - prebuild: true - - os: macos-14 + package: true + - os: macos-15 nodejs_arch: arm64 nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: darwin-arm64 - - os: macos-14 + - os: macos-15 nodejs_arch: arm64 nodejs_version: "^22.9.0" nodejs_version_major: 22 platform: darwin-arm64 - - os: windows-2019 + - os: windows-2022 nodejs_arch: x86 nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10 nodejs_version_major: 18 platform: win32-ia32 - prebuild: true - - os: windows-2019 + package: true + - os: windows-2022 nodejs_arch: x86 nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: win32-ia32 - - os: windows-2019 + - os: windows-2022 nodejs_arch: x86 nodejs_version: "^22.9.0" nodejs_version_major: 22 platform: win32-ia32 - - os: windows-2019 + - os: windows-2022 nodejs_arch: x64 nodejs_version: "^18.17.0" nodejs_version_major: 18 platform: win32-x64 - prebuild: true - - os: windows-2019 + package: true + - os: windows-2022 nodejs_arch: x64 nodejs_version: "^20.3.0" nodejs_version_major: 20 platform: win32-x64 - - os: windows-2019 + - os: windows-2022 nodejs_arch: x64 nodejs_version: "^22.9.0" nodejs_version_major: 22 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: - name: Dependencies (Rocky Linux glibc) if: contains(matrix.container, 'rockylinux') run: | - dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts - echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH + dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts + echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH - name: Dependencies (Linux musl) if: contains(matrix.container, 'alpine') run: apk add build-base git python3 font-noto --update-cache @@ -126,100 +164,144 @@ jobs: python-version: "3.12" - name: Dependencies (Node.js) if: "!contains(matrix.platform, 'linuxmusl')" - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.nodejs_version }} architecture: ${{ matrix.nodejs_arch }} - - name: Checkout - uses: actions/checkout@v4 - - name: Install - run: npm install --build-from-source - - name: Test - run: npm test - - name: Test packaging - run: | - npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - npm run clean - npm install --ignore-scripts - npm test - - name: Prebuild - if: matrix.prebuild && startsWith(github.ref, 'refs/tags/') - env: - prebuild_upload: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + - run: npm install + - run: npm run build + - run: npm run test-unit + - if: matrix.package + run: npm run package-from-local-build + - uses: actions/upload-artifact@v4 + if: matrix.package + with: + name: ${{ matrix.platform }} + path: npm/${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + build-linuxmusl-arm64: + permissions: + contents: read + needs: lint + name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}" + runs-on: ubuntu-24.04-arm + container: + image: ${{ matrix.container }} + volumes: + - /opt:/opt:rw,rshared + - /opt:/__e/node20:ro,rshared + 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: | - node -e "require('fs').cpSync('package.json', 'src/package.json')" - cd src - npx prebuild - github-runner-qemu: + sed -i "/^ID=/s/alpine/NotpineForGHA/" /etc/os-release + apk add nodejs --update-cache + mkdir /opt/bin + ln -s /usr/bin/node /opt/bin/node + - name: Dependencies + run: apk add build-base git python3 font-noto --update-cache + - uses: actions/checkout@v4 + - run: npm install + - run: npm run build + - run: npm run test-unit + - if: matrix.package + run: npm run package-from-local-build + - uses: actions/upload-artifact@v4 + if: matrix.package + with: + name: linuxmusl-arm64 + path: npm/linuxmusl-arm64 + retention-days: 1 + if-no-files-found: error + build-qemu: permissions: - contents: write - name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} - prebuild + contents: read + needs: lint + name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]" runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: include: - platform: linux-arm - distro: bullseye - run_on_arch: armv6 + base_image: "balenalib/rpi-raspbian:bullseye" nodejs_arch: armv6l nodejs_hostname: unofficial-builds.nodejs.org nodejs_version: "18.17.0" nodejs_version_major: 18 - platform: linux-s390x - distro: bullseye - run_on_arch: s390x + base_image: "--platform=linux/s390x s390x/debian:bookworm" nodejs_arch: s390x nodejs_hostname: nodejs.org nodejs_version: "18.17.0" nodejs_version_major: 18 - platform: linux-ppc64 - distro: bullseye - run_on_arch: ppc64le + base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm" nodejs_arch: ppc64le nodejs_hostname: nodejs.org nodejs_version: "18.17.0" nodejs_version_major: 18 + - platform: linux-riscv64 + base_image: "--platform=linux/riscv64 riscv64/debian:trixie" + compiler_flags: "-march=rv64gc" + nodejs_arch: riscv64 + nodejs_hostname: unofficial-builds.nodejs.org + nodejs_version: "20.19.5" + nodejs_version_major: 20 steps: - uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v3 with: - arch: ${{ matrix.run_on_arch }} - distro: ${{ matrix.distro }} + arch: none + distro: none + base_image: ${{ matrix.base_image }} env: | - prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}" + CFLAGS: "${{ matrix.compiler_flags }}" + CXXFLAGS: "${{ matrix.compiler_flags }}" run: | apt-get update apt-get install -y curl g++ git libatomic1 make python3 xz-utils mkdir /opt/nodejs curl --silent https://${{ matrix.nodejs_hostname }}/download/release/v${{ matrix.nodejs_version}}/node-v${{ matrix.nodejs_version}}-linux-${{ matrix.nodejs_arch }}.tar.xz | tar xJC /opt/nodejs --strip-components=1 export PATH=$PATH:/opt/nodejs/bin - npm install --build-from-source - npx mocha --no-config --spec=test/unit/io.js --timeout=30000 + npm install + npm run build + node --test test/unit/io.js npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}" - npm run clean - npm install --ignore-scripts - npx mocha --no-config --spec=test/unit/io.js --timeout=30000 - [[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true - github-runner-emscripten: + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.platform }} + path: npm/${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + build-emscripten: permissions: - contents: write - name: wasm32 - prebuild + contents: read + needs: lint + name: "build-wasm32 [package]" runs-on: ubuntu-24.04 - container: "emscripten/emsdk:4.0.6" + container: "emscripten/emsdk:4.0.18" steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Dependencies run: apt-get update && apt-get install -y pkg-config - name: Dependencies (Node.js) - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: "20" - - name: Install - run: emmake npm install --build-from-source + - run: npm install + - run: emmake npm run build - name: Verify emscripten versions match run: | EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten") @@ -227,19 +309,48 @@ jobs: echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS" echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP" test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP" - - name: Test - run: emmake npm test - - name: Test packaging - run: | - emmake npm run package-from-local-build - npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32" - npm run clean - rm -rf node_modules/@img/sharp-linux-x64 - npm install --cpu=wasm32 - npm test - - name: Prebuild - if: startsWith(github.ref, 'refs/tags/') - env: - npm_config_nodedir: emscripten - prebuild_upload: ${{ secrets.GITHUB_TOKEN }} - run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0 + - run: emmake npm run test-unit + - run: emmake npm run package-from-local-build + - uses: actions/upload-artifact@v4 + with: + name: wasm32 + path: npm/wasm32 + retention-days: 1 + if-no-files-found: error + release: + permissions: + contents: write + id-token: write + runs-on: ubuntu-24.04 + needs: + - build-native + - build-linuxmusl-arm64 + - 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 . + - uses: actions/setup-node@v5 + with: + node-version: '24' + - name: Create release notes + run: npm run package-release-notes + - 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') }} + bodyFile: release-notes.md + - name: Publish platform-specific npm packages + if: startsWith(github.ref, 'refs/tags/v') + run: cd npm && npm publish --workspaces --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }} + - name: Publish sharp npm package + if: startsWith(github.ref, 'refs/tags/v') + run: npm publish --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }} diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index f74916713..4db44a5b4 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -43,60 +43,60 @@ jobs: runtime: bun - name: darwin-x64-node-npm - runs-on: macos-13 + runs-on: macos-15-intel runtime: node package-manager: npm - name: darwin-x64-node-pnpm - runs-on: macos-13 + runs-on: macos-15-intel runtime: node package-manager: pnpm - name: darwin-x64-node-yarn - runs-on: macos-13 + runs-on: macos-15-intel runtime: node package-manager: yarn - name: darwin-x64-node-yarn-pnp - runs-on: macos-13 + runs-on: macos-15-intel runtime: node package-manager: yarn-pnp - name: darwin-x64-node-yarn-v1 - runs-on: macos-13 + runs-on: macos-15-intel runtime: node package-manager: yarn-v1 - name: darwin-x64-deno - runs-on: macos-13 + runs-on: macos-15-intel runtime: deno - name: darwin-x64-bun - runs-on: macos-13 + runs-on: macos-15-intel runtime: bun - name: win32-x64-node-npm - runs-on: windows-2019 + runs-on: windows-2022 runtime: node package-manager: npm - name: win32-x64-node-pnpm - runs-on: windows-2019 + runs-on: windows-2022 runtime: node package-manager: pnpm - name: win32-x64-node-yarn - runs-on: windows-2019 + runs-on: windows-2022 runtime: node package-manager: yarn - name: win32-x64-node-yarn-pnp - runs-on: windows-2019 + runs-on: windows-2022 runtime: node package-manager: yarn-pnp - name: win32-x64-node-yarn-v1 - runs-on: windows-2019 + runs-on: windows-2022 runtime: node package-manager: yarn-v1 - name: win32-x64-deno - runs-on: windows-2019 + runs-on: windows-2022 runtime: deno steps: - name: Install Node.js if: ${{ matrix.runtime == 'node' }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 20 - name: Install pnpm @@ -106,9 +106,9 @@ jobs: version: 8 - name: Install Deno if: ${{ matrix.runtime == 'deno' }} - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@v2 with: - deno-version: v1.x + deno-version: v2.x - name: Install Bun if: ${{ matrix.runtime == 'bun' }} uses: oven-sh/setup-bun@v2 @@ -117,7 +117,7 @@ jobs: - name: Version id: version - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | core.setOutput('semver', context.ref.replace('refs/tags/v','')) @@ -185,7 +185,9 @@ jobs: - name: Run with Deno if: ${{ matrix.runtime == 'deno' }} - run: deno run --allow-read --allow-ffi release.mjs + run: | + deno install + deno run --allow-env --allow-ffi --allow-read --allow-sys release.mjs - name: Run with Bun if: ${{ matrix.runtime == 'bun' }} diff --git a/.gitignore b/.gitignore index 6959cf624..82f73b3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ package-lock.json .firebase .astro docs/dist +release-notes.md diff --git a/.mocharc.jsonc b/.mocharc.jsonc deleted file mode 100644 index 06df4ac9f..000000000 --- a/.mocharc.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "parallel": true, - "slow": 1000, - "timeout": 30000, - "require": "./test/beforeEach.js", - "spec": "./test/unit/*.js" -} diff --git a/.prebuildrc b/.prebuildrc deleted file mode 100644 index 0a4ccd2ef..000000000 --- a/.prebuildrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "runtime": "napi", - "include-regex": "(sharp-.+\\.node|libvips-.+\\.dll)", - "prerelease": true, - "strip": true -} diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..7946049c8 --- /dev/null +++ b/biome.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "enabled": false, + "useEditorconfig": true + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 5c7209359..2209c3381 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -1,6 +1,9 @@ // @ts-check -import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; +import { defineConfig } from 'astro/config'; +import starlightAutoSidebar from 'starlight-auto-sidebar'; + +import { version } from '../package.json'; export default defineConfig({ site: 'https://sharp.pixelplumbing.com', @@ -68,12 +71,20 @@ export default defineConfig({ ] }, { label: 'Performance', slug: 'performance' }, - { label: 'Changelog', slug: 'changelog' } + { + label: 'Changelog', + collapsed: true, + autogenerate: { directory: 'changelog' } + } + ], + social: [ + { icon: 'openCollective', label: 'Open Collective', href: 'https://opencollective.com/libvips' }, + { icon: 'github', label: 'GitHub', href: 'https://github.com/lovell/sharp' } ], - social: { - openCollective: 'https://opencollective.com/libvips', - github: 'https://github.com/lovell/sharp' - } + plugins: [starlightAutoSidebar()] }) - ] + ], + redirects: { + '/changelog': `/changelog/v${version}` + } }); diff --git a/docs/build.mjs b/docs/build.mjs index de4d8b716..da43a5bf5 100644 --- a/docs/build.mjs +++ b/docs/build.mjs @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ import fs from 'node:fs/promises'; import path from 'node:path'; diff --git a/docs/package.json b/docs/package.json index 36bfd5b4b..7eaa41943 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,7 +11,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/starlight": "^0.32.3", - "astro": "^5.5.3" + "@astrojs/starlight": "^0.36.2", + "astro": "^5.15.3", + "starlight-auto-sidebar": "^0.1.3" } } diff --git a/docs/public/humans.txt b/docs/public/humans.txt index f8bd272fa..7119152ab 100644 --- a/docs/public/humans.txt +++ b/docs/public/humans.txt @@ -317,3 +317,12 @@ GitHub: https://github.com/florentzabera Name: Quentin Pinçon GitHub: https://github.com/qpincon + +Name: Hans Chen +GitHub: https://github.com/hans00 + +Name: Thibaut Patel +GitHub: https://github.com/tpatel + +Name: Maël Nison +GitHub: https://github.com/arcanis diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts index d9ee8c9d1..06cf12929 100644 --- a/docs/src/content.config.ts +++ b/docs/src/content.config.ts @@ -1,7 +1,10 @@ import { defineCollection } from 'astro:content'; import { docsLoader } from '@astrojs/starlight/loaders'; import { docsSchema } from '@astrojs/starlight/schema'; +import { autoSidebarLoader } from 'starlight-auto-sidebar/loader' +import { autoSidebarSchema } from 'starlight-auto-sidebar/schema' export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), + autoSidebar: defineCollection({ loader: autoSidebarLoader(), schema: autoSidebarSchema() }) }; diff --git a/docs/src/content/docs/api-channel.md b/docs/src/content/docs/api-channel.md index 298083487..214e8611d 100644 --- a/docs/src/content/docs/api-channel.md +++ b/docs/src/content/docs/api-channel.md @@ -8,7 +8,7 @@ title: Channel manipulation Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel. -See also [flatten](/api-operation#flatten). +See also [flatten](/api-operation/#flatten). **Example** @@ -61,6 +61,8 @@ const rgba = await sharp(rgb) Extract a single channel from a multi-channel image. +The output colourspace will be either `b-w` (8-bit) or `grey16` (16-bit). + **Throws**: diff --git a/docs/src/content/docs/api-colour.md b/docs/src/content/docs/api-colour.md index b1085ed2b..6fb81debd 100644 --- a/docs/src/content/docs/api-colour.md +++ b/docs/src/content/docs/api-colour.md @@ -80,7 +80,7 @@ as defined by [toColourspace](#tocolourspace). | Param | Type | Description | | --- | --- | --- | -| [colourspace] | string | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) | +| [colourspace] | string | pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://www.libvips.org/API/current/enum.Interpretation.html) | **Example** ```js @@ -123,7 +123,7 @@ By default output image will be web-friendly sRGB, with additional channels inte | Param | Type | Description | | --- | --- | --- | -| [colourspace] | string | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) | +| [colourspace] | string | output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html) | **Example** ```js diff --git a/docs/src/content/docs/api-composite.md b/docs/src/content/docs/api-composite.md index ea185ba0c..d4f9b8550 100644 --- a/docs/src/content/docs/api-composite.md +++ b/docs/src/content/docs/api-composite.md @@ -21,7 +21,7 @@ The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`, `hard-light`, `soft-light`, `difference`, `exclusion`. More information about blend modes can be found at -https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode +https://www.libvips.org/API/current/enum.BlendMode.html and https://www.cairographics.org/operators/ @@ -64,8 +64,8 @@ and https://www.cairographics.org/operators/ | [images[].raw.height] | Number | | | | [images[].raw.channels] | Number | | | | [images[].animated] | boolean | false | Set to `true` to read all frames/pages of an animated image. | -| [images[].failOn] | string | "'warning'" | @see [constructor parameters](/api-constructor#parameters) | -| [images[].limitInputPixels] | number \| boolean | 268402689 | @see [constructor parameters](/api-constructor#parameters) | +| [images[].failOn] | string | "'warning'" | @see [constructor parameters](/api-constructor/) | +| [images[].limitInputPixels] | number \| boolean | 268402689 | @see [constructor parameters](/api-constructor/) | **Example** ```js diff --git a/docs/src/content/docs/api-constructor.md b/docs/src/content/docs/api-constructor.md index 8010b58ba..3504239dc 100644 --- a/docs/src/content/docs/api-constructor.md +++ b/docs/src/content/docs/api-constructor.md @@ -44,24 +44,23 @@ where the overall height is the `pageHeight` multiplied by the number of `pages` | [options.ignoreIcc] | number | false | should the embedded ICC profile, if any, be ignored. | | [options.pages] | number | 1 | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. | | [options.page] | number | 0 | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. | -| [options.subifd] | number | -1 | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. | -| [options.level] | number | 0 | level to extract from a multi-level input (OpenSlide), zero based. | -| [options.pdfBackground] | string \| Object | | 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] | boolean | false | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. | | [options.raw] | Object | | describes raw pixel input image data. See `raw()` for pixel ordering. | | [options.raw.width] | number | | integral number of pixels wide. | | [options.raw.height] | number | | integral number of pixels high. | | [options.raw.channels] | number | | integral number of channels, between 1 and 4. | | [options.raw.premultiplied] | boolean | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) | +| [options.raw.pageHeight] | number | | The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`. | | [options.create] | Object | | describes a new image to be created. | | [options.create.width] | number | | integral number of pixels wide. | | [options.create.height] | number | | integral number of pixels high. | | [options.create.channels] | number | | integral number of channels, either 3 (RGB) or 4 (RGBA). | | [options.create.background] | string \| Object | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | +| [options.create.pageHeight] | number | | The pixel height of each page/frame for animated images, must be an integral factor of `create.height`. | | [options.create.noise] | Object | | describes a noise to be created. | | [options.create.noise.type] | string | | type of generated noise, currently only `gaussian` is supported. | -| [options.create.noise.mean] | number | | mean of pixels in generated noise. | -| [options.create.noise.sigma] | number | | standard deviation of pixels in generated noise. | +| [options.create.noise.mean] | number | 128 | Mean value of pixels in the generated noise. | +| [options.create.noise.sigma] | number | 30 | Standard deviation of pixel values in the generated noise. | | [options.text] | Object | | describes a new text image to be created. | | [options.text.text] | string | | text to render as a UTF-8 string. It can contain Pango markup, for example `LeMonde`. | | [options.text.font] | string | | font name to render with. | @@ -81,6 +80,17 @@ where the overall height is the `pageHeight` multiplied by the number of `pages` | [options.join.background] | string \| Object | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | | [options.join.halign] | string | "'left'" | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). | | [options.join.valign] | string | "'top'" | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). | +| [options.tiff] | Object | | Describes TIFF specific options. | +| [options.tiff.subifd] | number | -1 | Sub Image File Directory to extract for OME-TIFF, defaults to main image. | +| [options.svg] | Object | | Describes SVG specific options. | +| [options.svg.stylesheet] | string | | Custom CSS for SVG input, applied with a User Origin during the CSS cascade. | +| [options.svg.highBitdepth] | boolean | false | 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] | Object | | Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. | +| [options.pdf.background] | string \| Object | | 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] | Object | | Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide. | +| [options.openSlide.level] | number | 0 | Level to extract from a multi-level input, zero based. | +| [options.jp2] | Object | | Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG. | +| [options.jp2.oneshot] | boolean | false | Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. | **Example** ```js diff --git a/docs/src/content/docs/api-input.md b/docs/src/content/docs/api-input.md index 3e1f9a7da..8477f1ff5 100644 --- a/docs/src/content/docs/api-input.md +++ b/docs/src/content/docs/api-input.md @@ -13,17 +13,17 @@ It does not take into consideration any operations to be applied to the output i such as resize or rotate. Dimensions in the response will respect the `page` and `pages` properties of the -[constructor parameters](/api-constructor#parameters). +[constructor parameters](/api-constructor/). A `Promise` is returned when `callback` is not provided. -- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` +- `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff` - `size`: Total size of image in bytes, for Stream and Buffer input only - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) -- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation) +- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html) - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK -- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat) +- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/enum.BandFormat.html) - `density`: Number of pixels per inch (DPI), if present - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan @@ -46,6 +46,7 @@ A `Promise` is returned when `callback` is not provided. - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present - `iptc`: Buffer containing raw IPTC 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 - `formatMagick`: String containing format for images loaded via *magick - `comments`: Array of keyword/text pairs representing PNG text blocks, if present. diff --git a/docs/src/content/docs/api-operation.md b/docs/src/content/docs/api-operation.md index dea7c79e3..abcb41fc1 100644 --- a/docs/src/content/docs/api-operation.md +++ b/docs/src/content/docs/api-operation.md @@ -179,7 +179,7 @@ When used without parameters, performs a fast, mild sharpen of the output image. When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available. -See [libvips sharpen](https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen) operation. +See [libvips sharpen](https://www.libvips.org/API/current/method.Image.sharpen.html) operation. **Throws**: diff --git a/docs/src/content/docs/api-output.md b/docs/src/content/docs/api-output.md index 945da137f..5acd2fadb 100644 --- a/docs/src/content/docs/api-output.md +++ b/docs/src/content/docs/api-output.md @@ -201,7 +201,7 @@ const dataWithMergedExif = await sharp(inputWithExif) Keep ICC profile from the input image in the output image. -Where necessary, will attempt to convert the output colour space to match the profile. +When input and output colour spaces differ, use with [toColourspace](/api-colour/#tocolourspace) and optionally [pipelineColourspace](/api-colour/#pipelinecolourspace). **Since**: 0.33.0 @@ -211,6 +211,14 @@ const outputWithIccProfile = await sharp(inputWithIccProfile) .keepIccProfile() .toBuffer(); ``` +**Example** +```js +const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile) + .pipelineColourspace('cmyk') + .toColourspace('cmyk') + .keepIccProfile() + .toBuffer(); +``` ## withIccProfile @@ -242,6 +250,57 @@ const outputWithP3 = await sharp(input) ``` +## keepXmp +> keepXmp() ⇒ Sharp + +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) ⇒ Sharp + +Set XMP metadata in the output image. + +Supported by PNG, JPEG, WebP, and TIFF output. + + +**Throws**: + +- Error Invalid parameters + +**Since**: 0.34.3 + +| Param | Type | Description | +| --- | --- | --- | +| xmp | string | String containing XMP metadata to be embedded in the output image. | + +**Example** +```js +const xmpString = ` + + + + + John Doe + + + `; + +const data = await sharp(input) + .withXmp(xmpString) + .toBuffer(); +``` + + ## keepMetadata > keepMetadata() ⇒ Sharp @@ -379,7 +438,7 @@ Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. Set `palette` to `true` for slower, indexed PNG output. For 16 bits per pixel output, convert to `rgb16` via -[toColourspace](/api-colour#tocolourspace). +[toColourspace](/api-colour/#tocolourspace). **Throws**: @@ -496,6 +555,7 @@ The palette of the input image will be re-used if possible. | [options.dither] | number | 1.0 | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) | | [options.interFrameMaxError] | number | 0 | maximum inter-frame error for transparency, between 0 (lossless) and 32 | | [options.interPaletteMaxError] | number | 3 | maximum inter-palette error for palette reuse, between 0 and 256 | +| [options.keepDuplicateFrames] | boolean | false | keep duplicate frames in the output instead of combining them | | [options.loop] | number | 0 | number of animation iterations, use 0 for infinite animation | | [options.delay] | number \| Array.<number> | | delay(s) between animation frames (in milliseconds) | | [options.force] | boolean | true | force GIF output, otherwise attempt to use input format | @@ -537,7 +597,7 @@ Use these JP2 options for output image. Requires libvips compiled with support for OpenJPEG. The prebuilt binaries do not include this - see -[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips). +[installing a custom libvips](/install#custom-libvips). **Throws**: @@ -594,6 +654,7 @@ instead of providing `xres` and `yres` in pixels/mm. | [options.quality] | number | 80 | quality, integer 1-100 | | [options.force] | boolean | true | force TIFF output, otherwise attempt to use input format | | [options.compression] | string | "'jpeg'" | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k | +| [options.bigtiff] | boolean | false | use BigTIFF variant (has no effect when compression is none) | | [options.predictor] | string | "'horizontal'" | compression predictor options: none, horizontal, float | | [options.pyramid] | boolean | false | write an image pyramid | | [options.tile] | boolean | false | write a tiled tiff | @@ -626,6 +687,9 @@ Use these AVIF options for output image. AVIF image sequences are not supported. Prebuilt binaries support a bitdepth of 8 only. +This feature is experimental on the Windows ARM64 platform +and requires a CPU with ARM64v8.4 or later. + **Throws**: @@ -698,7 +762,7 @@ This feature is experimental, please do not use in production systems. Requires libvips compiled with support for libjxl. The prebuilt binaries do not include this - see -[installing a custom libvips](https://sharp.pixelplumbing.com/install#custom-libvips). +[installing a custom libvips](/install/#custom-libvips). **Throws**: diff --git a/docs/src/content/docs/api-resize.md b/docs/src/content/docs/api-resize.md index da7ac53bf..6452c3455 100644 --- a/docs/src/content/docs/api-resize.md +++ b/docs/src/content/docs/api-resize.md @@ -38,6 +38,8 @@ Possible downsizing kernels are: - `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`. - `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. Downsampling kernels without a matching upsampling interpolator map to `cubic`. diff --git a/docs/src/content/docs/api-utility.md b/docs/src/content/docs/api-utility.md index 8a7ef2483..73190fd01 100644 --- a/docs/src/content/docs/api-utility.md +++ b/docs/src/content/docs/api-utility.md @@ -113,15 +113,9 @@ Some image format libraries spawn additional threads, e.g. libaom manages its own 4 threads when encoding AVIF images, and these are independent of the value set here. -The maximum number of images that sharp can process in parallel -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. +:::note +Further [control over performance](/performance/) is available. +::: **Returns**: number - concurrency diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md deleted file mode 100644 index d45e433e8..000000000 --- a/docs/src/content/docs/changelog.md +++ /dev/null @@ -1,2133 +0,0 @@ ---- -title: Changelog ---- - -## v0.34 - *hat* - -Requires libvips v8.16.1 - -### v0.34.2 - TBD - -* 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) - -* TypeScript: Ensure `smartDeblock` property is included in WebP definition. - [#4387](https://github.com/lovell/sharp/pull/4387) - [@Stephen-X](https://github.com/Stephen-X) - -### v0.34.1 - 7th April 2025 - -* TypeScript: Ensure new `autoOrient` property is optional. - [#4362](https://github.com/lovell/sharp/pull/4362) - [@styfle](https://github.com/styfle) - -### v0.34.0 - 4th April 2025 - -* Breaking: Support array of input images to be joined or animated. - [#1580](https://github.com/lovell/sharp/issues/1580) - -* Breaking: Ensure `removeAlpha` removes all alpha channels. - [#2266](https://github.com/lovell/sharp/issues/2266) - -* Breaking: Non-animated GIF output defaults to no-loop instead of loop-forever. - [#3394](https://github.com/lovell/sharp/issues/3394) - -* Breaking: Support `info.size` on wide-character systems via upgrade to C++17. - [#3943](https://github.com/lovell/sharp/issues/3943) - -* Breaking: Ensure `background` metadata can be parsed by `color` package. - [#4090](https://github.com/lovell/sharp/issues/4090) - -* Add `isPalette` and `bitsPerSample` to metadata, deprecate `paletteBitDepth`. - -* Expose WebP `smartDeblock` output option. - -* Prevent use of linux-x64 binaries with v1 microarchitecture. - -* Add `autoOrient` operation and constructor option. - [#4151](https://github.com/lovell/sharp/pull/4151) - [@happycollision](https://github.com/happycollision) - -* TypeScript: Ensure channel counts use the correct range. - [#4197](https://github.com/lovell/sharp/pull/4197) - [@DavidVaness](https://github.com/DavidVaness) - -* Improve support for ppc64le architecture. - [#4203](https://github.com/lovell/sharp/pull/4203) - [@sumitd2](https://github.com/sumitd2) - -* Add `pdfBackground` constructor property. - [#4207](https://github.com/lovell/sharp/pull/4207) - [@calebmer](https://github.com/calebmer) - -* Expose erode and dilate operations. - [#4243](https://github.com/lovell/sharp/pull/4243) - [@qpincon](https://github.com/qpincon) - -* Add support for RGBE images. Requires libvips compiled with radiance support. - [#4316](https://github.com/lovell/sharp/pull/4316) - [@florentzabera](https://github.com/florentzabera) - -* Allow wide-gamut HEIF output at higher bitdepths. - [#4344](https://github.com/lovell/sharp/issues/4344) - -## v0.33 - *gauge* - -Requires libvips v8.15.3 - -### v0.33.5 - 16th August 2024 - -* Upgrade to libvips v8.15.3 for upstream bug fixes. - -* Add `pageHeight` and `pages` to response of multi-page output. - [#3411](https://github.com/lovell/sharp/issues/3411) - -* Ensure option to force use of a globally-installed libvips works correctly. - [#4111](https://github.com/lovell/sharp/pull/4111) - [@project0](https://github.com/project0) - -* Minimise use of `engines` property to improve yarn v1 support. - [#4130](https://github.com/lovell/sharp/issues/4130) - -* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries. - [#4132](https://github.com/lovell/sharp/issues/4132) - -* Add support to recomb operation for 4x4 matrices. - [#4147](https://github.com/lovell/sharp/pull/4147) - [@ton11797](https://github.com/ton11797) - -* Expose PNG text chunks as `comments` metadata. - [#4157](https://github.com/lovell/sharp/pull/4157) - [@nkeynes](https://github.com/nkeynes) - -* Expose optional `precision` and `minAmplitude` parameters of `blur` operation. - [#4168](https://github.com/lovell/sharp/pull/4168) - [#4172](https://github.com/lovell/sharp/pull/4172) - [@marcosc90](https://github.com/marcosc90) - -* Ensure `keepIccProfile` avoids colour transformation where possible. - [#4186](https://github.com/lovell/sharp/issues/4186) - -* TypeScript: `chromaSubsampling` metadata is optional. - [#4191](https://github.com/lovell/sharp/pull/4191) - [@DavidVaness](https://github.com/DavidVaness) - -### v0.33.4 - 16th May 2024 - -* Remove experimental status from `pipelineColourspace`. - -* Reduce default concurrency when musl thread over-subscription detected. - -* TypeScript: add missing definitions for `OverlayOptions`. - [#4048](https://github.com/lovell/sharp/pull/4048) - [@ike-gg](https://github.com/ike-gg) - -* Install: add advanced option to force use of a globally-installed libvips. - [#4060](https://github.com/lovell/sharp/issues/4060) - -* Expose `bilinear` resizing kernel (and interpolator). - [#4061](https://github.com/lovell/sharp/issues/4061) - -* Ensure `extend` operation stays sequential for multi-page TIFF (regression in 0.32.0). - [#4069](https://github.com/lovell/sharp/issues/4069) - -* Tighten validation of constructor `text` integer properties. - [#4071](https://github.com/lovell/sharp/issues/4071) - -* Simplify internal StaySequential logic. - [#4074](https://github.com/lovell/sharp/pull/4074) - [@kleisauke](https://github.com/kleisauke) - -* Ensure negate operation occurs after profile conversion. - [#4096](https://github.com/lovell/sharp/pull/4096) - [@adriaanmeuris](https://github.com/adriaanmeuris) - -### v0.33.3 - 23rd March 2024 - -* Upgrade to libvips v8.15.2 for upstream bug fixes. - -* Ensure `keepIccProfile` retains P3 and CMYK input profiles. - [#3906](https://github.com/lovell/sharp/issues/3906) - [#4008](https://github.com/lovell/sharp/issues/4008) - -* Ensure `text.wrap` property can accept `word-char` as value. - [#4028](https://github.com/lovell/sharp/pull/4028) - [@yolopunk](https://github.com/yolopunk) - -* Ensure `clone` takes a deep copy of existing options. - [#4029](https://github.com/lovell/sharp/issues/4029) - -* Add `bitdepth` option to `heif` output (prebuilt binaries support 8-bit only). - [#4036](https://github.com/lovell/sharp/pull/4036) - [@mertalev](https://github.com/mertalev) - -### v0.33.2 - 12th January 2024 - -* Upgrade to libvips v8.15.1 for upstream bug fixes. - -* TypeScript: add definition for `keepMetadata`. - [#3914](https://github.com/lovell/sharp/pull/3914) - [@abhi0498](https://github.com/abhi0498) - -* Ensure `extend` operation stays sequential when copying (regression in 0.32.0). - [#3928](https://github.com/lovell/sharp/issues/3928) - -* Improve error handling for unsupported multi-page rotation. - [#3940](https://github.com/lovell/sharp/issues/3940) - -### v0.33.1 - 17th December 2023 - -* Add support for Yarn Plug'n'Play filesystem layout. - [#3888](https://github.com/lovell/sharp/issues/3888) - -* Emit warning when attempting to use invalid ICC profiles. - [#3895](https://github.com/lovell/sharp/issues/3895) - -* Ensure `VIPS_NOVECTOR` environment variable is respected. - [#3897](https://github.com/lovell/sharp/pull/3897) - [@icetee](https://github.com/icetee) - -### v0.33.0 - 29th November 2023 - -* Drop support for Node.js 14 and 16, now requires Node.js ^18.17.0 or >= 20.3.0 - -* Prebuilt binaries distributed via npm registry and installed via package manager. - -* Building from source requires dependency on `node-addon-api`. - -* Remove `sharp.vendor`. - -* Partially deprecate `withMetadata()`, use `withExif()` and `withIccProfile()`. - -* Add experimental support for WebAssembly-based runtimes. - [@RReverser](https://github.com/RReverser) - -* Options for `trim` operation must be an Object, add new `lineArt` option. - [#2363](https://github.com/lovell/sharp/issues/2363) - -* Improve luminance of `tint` operation with weighting function. - [#3338](https://github.com/lovell/sharp/issues/3338) - [@jcupitt](https://github.com/jcupitt) - -* Ensure all `Error` objects contain a `stack` property. - [#3653](https://github.com/lovell/sharp/issues/3653) - -* Make `compression` option of `heif` mandatory to help reduce HEIF vs HEIC confusion. - [#3740](https://github.com/lovell/sharp/issues/3740) - -* Ensure correct interpretation of 16-bit raw input. - [#3808](https://github.com/lovell/sharp/issues/3808) - -* Add support for `miniswhite` when using TIFF output. - [#3812](https://github.com/lovell/sharp/pull/3812) - [@dnsbty](https://github.com/dnsbty) - -* TypeScript: add missing definition for `withMetadata` boolean. - [#3823](https://github.com/lovell/sharp/pull/3823) - [@uhthomas](https://github.com/uhthomas) - -* Add more fine-grained control over output metadata. - [#3824](https://github.com/lovell/sharp/issues/3824) - -* Ensure multi-page extract remains sequential. - [#3837](https://github.com/lovell/sharp/issues/3837) - -## v0.32 - *flow* - -Requires libvips v8.14.5 - -### v0.32.6 - 18th September 2023 - -* Upgrade to libvips v8.14.5 for upstream bug fixes. - -* Ensure composite tile images are fully decoded (regression in 0.32.0). - [#3767](https://github.com/lovell/sharp/issues/3767) - -* Ensure `withMetadata` can add ICC profiles to RGB16 output. - [#3773](https://github.com/lovell/sharp/issues/3773) - -* Ensure `withMetadata` does not reduce 16-bit images to 8-bit (regression in 0.32.5). - [#3773](https://github.com/lovell/sharp/issues/3773) - -* TypeScript: Add definitions for block and unblock. - [#3799](https://github.com/lovell/sharp/pull/3799) - [@ldrick](https://github.com/ldrick) - -### v0.32.5 - 15th August 2023 - -* Upgrade to libvips v8.14.4 for upstream bug fixes. - -* TypeScript: Add missing `WebpPresetEnum` to definitions. - [#3748](https://github.com/lovell/sharp/pull/3748) - [@pilotso11](https://github.com/pilotso11) - -* Ensure compilation using musl v1.2.4. - [#3755](https://github.com/lovell/sharp/pull/3755) - [@kleisauke](https://github.com/kleisauke) - -* Ensure resize with a `fit` of `inside` respects 90/270 degree rotation. - [#3756](https://github.com/lovell/sharp/issues/3756) - -* TypeScript: Ensure `minSize` property of `WebpOptions` is boolean. - [#3758](https://github.com/lovell/sharp/pull/3758) - [@sho-xizz](https://github.com/sho-xizz) - -* Ensure `withMetadata` adds default sRGB profile. - [#3761](https://github.com/lovell/sharp/issues/3761) - -### v0.32.4 - 21st July 2023 - -* Upgrade to libvips v8.14.3 for upstream bug fixes. - -* Expose ability to (un)block low-level libvips operations by name. - -* Prebuilt binaries: restore support for tile-based output. - [#3581](https://github.com/lovell/sharp/issues/3581) - -### v0.32.3 - 14th July 2023 - -* Expose `preset` option for WebP output. - [#3639](https://github.com/lovell/sharp/issues/3639) - -* Ensure decoding remains sequential for all operations (regression in 0.32.2). - [#3725](https://github.com/lovell/sharp/issues/3725) - -### v0.32.2 - 11th July 2023 - -* Limit HEIF output dimensions to 16384x16384, matches libvips. - -* Ensure exceptions are not thrown when terminating. - [#3569](https://github.com/lovell/sharp/issues/3569) - -* Ensure the same access method is used for all inputs (regression in 0.32.0). - [#3669](https://github.com/lovell/sharp/issues/3669) - -* Improve detection of jp2 filename extensions. - [#3674](https://github.com/lovell/sharp/pull/3674) - [@bianjunjie1981](https://github.com/bianjunjie1981) - -* Guard use of smartcrop premultiplied option to prevent warning (regression in 0.32.1). - [#3710](https://github.com/lovell/sharp/issues/3710) - -* Prevent over-compute in affine-based rotate before resize. - [#3722](https://github.com/lovell/sharp/issues/3722) - -* Allow sequential read for EXIF-based auto-orientation. - [#3725](https://github.com/lovell/sharp/issues/3725) - -### v0.32.1 - 27th April 2023 - -* Add experimental `unflatten` operation. - [#3461](https://github.com/lovell/sharp/pull/3461) - [@antonmarsden](https://github.com/antonmarsden) - -* Ensure use of `flip` operation forces random access read (regression in 0.32.0). - [#3600](https://github.com/lovell/sharp/issues/3600) - -* Ensure `linear` operation works with 16-bit input (regression in 0.31.3). - [#3605](https://github.com/lovell/sharp/issues/3605) - -* Install: ensure proxy URLs are logged correctly. - [#3615](https://github.com/lovell/sharp/pull/3615) - [@TomWis97](https://github.com/TomWis97) - -* Ensure profile-less CMYK to CMYK roundtrip skips colourspace conversion. - [#3620](https://github.com/lovell/sharp/issues/3620) - -* Add support for `modulate` operation when using non-sRGB pipeline colourspace. - [#3620](https://github.com/lovell/sharp/issues/3620) - -* Ensure `trim` operation works with CMYK images (regression in 0.31.0). - [#3636](https://github.com/lovell/sharp/issues/3636) - -* Install: coerce libc version to semver. - [#3641](https://github.com/lovell/sharp/issues/3641) - -### v0.32.0 - 24th March 2023 - -* Default to using sequential rather than random access read where possible. - -* Replace GIF output `optimise` / `optimize` option with `reuse`. - -* Add `progressive` option to GIF output for interlacing. - -* Add `wrap` option to text image creation. - -* Add `formatMagick` property to metadata of images loaded via *magick. - -* Prefer integer (un)premultiply for faster resizing of RGBA images. - -* Add `ignoreIcc` input option to ignore embedded ICC profile. - -* Allow use of GPS (IFD3) EXIF metadata. - [#2767](https://github.com/lovell/sharp/issues/2767) - -* TypeScript definitions are now maintained and published directly, deprecating the `@types/sharp` package. - [#3369](https://github.com/lovell/sharp/issues/3369) - -* Prebuilt binaries: ensure macOS 10.13+ support, as documented. - [#3438](https://github.com/lovell/sharp/issues/3438) - -* Prebuilt binaries: prevent use of glib slice allocator, improves QEMU support. - [#3448](https://github.com/lovell/sharp/issues/3448) - -* Add focus point coordinates to output when using attention based crop. - [#3470](https://github.com/lovell/sharp/pull/3470) - [@ejoebstl](https://github.com/ejoebstl) - -* Expose sharp version as `sharp.versions.sharp`. - [#3471](https://github.com/lovell/sharp/issues/3471) - -* Respect `fastShrinkOnLoad` resize option for WebP input. - [#3516](https://github.com/lovell/sharp/issues/3516) - -* Reduce sharpen `sigma` maximum from 10000 to 10. - [#3521](https://github.com/lovell/sharp/issues/3521) - -* Add support for `ArrayBuffer` input. - [#3548](https://github.com/lovell/sharp/pull/3548) - [@kapouer](https://github.com/kapouer) - -* Add support to `extend` operation for `extendWith` to allow copy/mirror/repeat. - [#3556](https://github.com/lovell/sharp/pull/3556) - [@janaz](https://github.com/janaz) - -* Ensure all async JS callbacks are wrapped to help avoid possible race condition. - [#3569](https://github.com/lovell/sharp/issues/3569) - -* Prebuilt binaries: support for tile-based output temporarily removed due to licensing issue. - [#3581](https://github.com/lovell/sharp/issues/3581) - -* Add support to `normalise` for `lower` and `upper` percentiles. - [#3583](https://github.com/lovell/sharp/pull/3583) - [@LachlanNewman](https://github.com/LachlanNewman) - -## v0.31 - *eagle* - -Requires libvips v8.13.3 - -### v0.31.3 - 21st December 2022 - -* Add experimental support for JPEG-XL images. Requires libvips compiled with libjxl. - [#2731](https://github.com/lovell/sharp/issues/2731) - -* Add runtime detection of V8 memory cage, ensures compatibility with Electron 21 onwards. - [#3384](https://github.com/lovell/sharp/issues/3384) - -* Expose `interFrameMaxError` and `interPaletteMaxError` GIF optimisation properties. - [#3401](https://github.com/lovell/sharp/issues/3401) - -* Allow installation on Linux with glibc patch versions e.g. Fedora 38. - [#3423](https://github.com/lovell/sharp/issues/3423) - -* Expand range of existing `sharpen` parameters to match libvips. - [#3427](https://github.com/lovell/sharp/issues/3427) - -* Prevent possible race condition awaiting metadata of Stream-based input. - [#3451](https://github.com/lovell/sharp/issues/3451) - -* Improve `extractChannel` support for 16-bit output colourspaces. - [#3453](https://github.com/lovell/sharp/issues/3453) - -* Ignore `sequentialRead` option when calculating image statistics. - [#3462](https://github.com/lovell/sharp/issues/3462) - -* Small performance improvement for operations that introduce a non-opaque background. - [#3465](https://github.com/lovell/sharp/issues/3465) - -* Ensure integral output of `linear` operation. - [#3468](https://github.com/lovell/sharp/issues/3468) - -### v0.31.2 - 4th November 2022 - -* Upgrade to libvips v8.13.3 for upstream bug fixes. - -* Ensure manual flip, rotate, resize operation ordering (regression in 0.31.1) - [#3391](https://github.com/lovell/sharp/issues/3391) - -* Ensure auto-rotation works without resize (regression in 0.31.1) - [#3422](https://github.com/lovell/sharp/issues/3422) - -### v0.31.1 - 29th September 2022 - -* Upgrade to libvips v8.13.2 for upstream bug fixes. - -* Ensure `close` event occurs after `end` event for Stream-based output. - [#3313](https://github.com/lovell/sharp/issues/3313) - -* Ensure `limitInputPixels` constructor option uses uint64. - [#3349](https://github.com/lovell/sharp/pull/3349) - [@marcosc90](https://github.com/marcosc90) - -* Ensure auto-rotation works with shrink-on-load and extract (regression in 0.31.0). - [#3352](https://github.com/lovell/sharp/issues/3352) - -* Ensure AVIF output is always 8-bit. - [#3358](https://github.com/lovell/sharp/issues/3358) - -* Ensure greyscale images can be trimmed (regression in 0.31.0). - [#3386](https://github.com/lovell/sharp/issues/3386) - -### v0.31.0 - 5th September 2022 - -* Drop support for Node.js 12, now requires Node.js >= 14.15.0. - -* GIF output now re-uses input palette if possible. Use `reoptimise` option to generate a new palette. - -* Add WebP `minSize` and `mixed` options for greater control over animation frames. - -* Remove previously-deprecated WebP `reductionEffort` and HEIF `speed` options. Use `effort` to control these. - -* The `flip` and `flop` operations will now occur before the `rotate` operation. - -* Improve `normalise` operation with use of histogram. - [#200](https://github.com/lovell/sharp/issues/200) - -* Use combined bounding box of alpha and non-alpha channels for `trim` operation. - [#2166](https://github.com/lovell/sharp/issues/2166) - -* Add Buffer and Stream support to tile-based output. - [#2238](https://github.com/lovell/sharp/issues/2238) - -* Add input `fileSuffix` and output `alias` to `format` information. - [#2642](https://github.com/lovell/sharp/issues/2642) - -* Re-introduce support for greyscale ICC profiles (temporarily removed in 0.30.2). - [#3114](https://github.com/lovell/sharp/issues/3114) - -* Add support for WebP and PackBits `compression` options with TIFF output. - [#3198](https://github.com/lovell/sharp/issues/3198) - -* Ensure OpenSlide and FITS input works with custom libvips. - [#3226](https://github.com/lovell/sharp/issues/3226) - -* Ensure `trim` operation is a no-op when it would reduce an image to nothing. - [#3223](https://github.com/lovell/sharp/issues/3223) - -* Expose `vips_text` to create an image containing rendered text. - [#3252](https://github.com/lovell/sharp/pull/3252) - [@brahima](https://github.com/brahima) - -* Ensure only properties owned by the `withMetadata` EXIF Object are parsed. - [#3292](https://github.com/lovell/sharp/issues/3292) - -* Expand `linear` operation to allow use of per-channel arrays. - [#3303](https://github.com/lovell/sharp/pull/3303) - [@antonmarsden](https://github.com/antonmarsden) - -* Ensure the order of `rotate`, `resize` and `extend` operations is respected where possible. - Emit warnings when previous calls in the same pipeline will be ignored. - [#3319](https://github.com/lovell/sharp/issues/3319) - -* Ensure PNG bitdepth can be set for non-palette output. - [#3322](https://github.com/lovell/sharp/issues/3322) - -* Add trim option to provide a specific background colour. - [#3332](https://github.com/lovell/sharp/pull/3332) - [@mart-jansink](https://github.com/mart-jansink) - -* Ensure resized image is unpremultiplied before composite. - [#3334](https://github.com/lovell/sharp/issues/3334) - -## v0.30 - *dresser* - -Requires libvips v8.12.2 - -### v0.30.7 - 22nd June 2022 - -* Ensure tiled composition always works with outside resizing. - [#3227](https://github.com/lovell/sharp/issues/3227) - -* Allow WebP encoding effort of 0. - [#3261](https://github.com/lovell/sharp/pull/3261) - [@AlexanderTheGrey](https://github.com/AlexanderTheGrey) - -* Prevent upsampling via libwebp. - [#3267](https://github.com/lovell/sharp/pull/3267) - [@blacha](https://github.com/blacha) - -### v0.30.6 - 30th May 2022 - -* Allow values for `limitInputPixels` larger than 32-bit. - [#3238](https://github.com/lovell/sharp/issues/3238) - -* Ensure brew-installed `vips` can be detected (regression in 0.30.5). - [#3239](https://github.com/lovell/sharp/issues/3239) - -### v0.30.5 - 23rd May 2022 - -* Install: pass `PKG_CONFIG_PATH` via env rather than substitution. - [@dwisiswant0](https://github.com/dwisiswant0) - -* Add support for `--libc` flag to improve cross-platform installation. - [#3160](https://github.com/lovell/sharp/pull/3160) - [@joonamo](https://github.com/joonamo) - -* Allow installation of prebuilt libvips binaries from filesystem. - [#3196](https://github.com/lovell/sharp/pull/3196) - [@ankurparihar](https://github.com/ankurparihar) - -* Fix rotate-then-extract for EXIF orientation 2. - [#3218](https://github.com/lovell/sharp/pull/3218) - [@jakob0fischl](https://github.com/jakob0fischl) - -### v0.30.4 - 18th April 2022 - -* Increase control over sensitivity to invalid images via `failOn`, deprecate `failOnError` (equivalent to `failOn: 'warning'`). - -* Ensure `create` input image has correct bit depth and colour space. - [#3139](https://github.com/lovell/sharp/issues/3139) - -* Add support for `TypedArray` input with `byteOffset` and `length`. - [#3146](https://github.com/lovell/sharp/pull/3146) - [@codepage949](https://github.com/codepage949) - -* Improve error message when attempting to render SVG input greater than 32767x32767. - [#3167](https://github.com/lovell/sharp/issues/3167) - -* Add missing file name to 'Input file is missing' error message. - [#3178](https://github.com/lovell/sharp/pull/3178) - [@Brodan](https://github.com/Brodan) - -### v0.30.3 - 14th March 2022 - -* Allow `sharpen` options to be provided more consistently as an Object. - [#2561](https://github.com/lovell/sharp/issues/2561) - -* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation. - [#2935](https://github.com/lovell/sharp/issues/2935) - -* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2). - [#3118](https://github.com/lovell/sharp/issues/3118) - -### v0.30.2 - 2nd March 2022 - -* Improve performance and accuracy when compositing multiple images. - [#2286](https://github.com/lovell/sharp/issues/2286) - -* Expand pkgconfig search path for wider BSD support. - [#3106](https://github.com/lovell/sharp/issues/3106) - -* Ensure Windows C++ runtime is linked statically (regression in 0.30.0). - [#3110](https://github.com/lovell/sharp/pull/3110) - [@kleisauke](https://github.com/kleisauke) - -* Temporarily ignore greyscale ICC profiles to workaround lcms bug. - [#3112](https://github.com/lovell/sharp/issues/3112) - -### v0.30.1 - 9th February 2022 - -* Allow use of `toBuffer` and `toFile` on the same instance. - [#3044](https://github.com/lovell/sharp/issues/3044) - -* Skip shrink-on-load for known libjpeg rounding errors. - [#3066](https://github.com/lovell/sharp/issues/3066) - [@kleisauke](https://github.com/kleisauke) - -* Ensure withoutReduction does not interfere with contain/crop/embed. - [#3081](https://github.com/lovell/sharp/pull/3081) - [@kleisauke](https://github.com/kleisauke) - -* Ensure affine interpolator is correctly finalised. - [#3083](https://github.com/lovell/sharp/pull/3083) - [@kleisauke](https://github.com/kleisauke) - -### v0.30.0 - 1st February 2022 - -* Add support for GIF output to prebuilt binaries. - -* Reduce minimum Linux ARM64v8 glibc requirement to 2.17. - -* Verify prebuilt binaries with a Subresource Integrity check. - -* Standardise WebP `effort` option name, deprecate `reductionEffort`. - -* Standardise HEIF `effort` option name, deprecate `speed`. - -* Add support for IIIF v3 tile-based output. - -* Expose control over CPU effort for palette-based PNG output. - [#2541](https://github.com/lovell/sharp/issues/2541) - -* Improve animated (multi-page) image resize and extract. - [#2789](https://github.com/lovell/sharp/pull/2789) - [@kleisauke](https://github.com/kleisauke) - -* Expose platform and architecture of vendored binaries as `sharp.vendor`. - [#2928](https://github.com/lovell/sharp/issues/2928) - -* Ensure 16-bit PNG output uses correct bitdepth. - [#2958](https://github.com/lovell/sharp/pull/2958) - [@gforge](https://github.com/gforge) - -* Properly emit close events for duplex streams. - [#2976](https://github.com/lovell/sharp/pull/2976) - [@driannaude](https://github.com/driannaude) - -* Expose `unlimited` option for SVG and PNG input, switches off safety features. - [#2984](https://github.com/lovell/sharp/issues/2984) - -* Add `withoutReduction` option to resize operation. - [#3006](https://github.com/lovell/sharp/pull/3006) - [@christopherbradleybanks](https://github.com/christopherbradleybanks) - -* Add `resolutionUnit` as `tiff` option and expose in metadata. - [#3023](https://github.com/lovell/sharp/pull/3023) - [@ompal-sisodiya](https://github.com/ompal-sisodiya) - -* Ensure rotate-then-extract works with EXIF mirroring. - [#3024](https://github.com/lovell/sharp/issues/3024) - -## v0.29 - *circle* - -Requires libvips v8.11.3 - -### v0.29.3 - 14th November 2021 - -* Ensure correct dimensions when containing image resized to 1px. - [#2951](https://github.com/lovell/sharp/issues/2951) - -* Impute TIFF `xres`/`yres` from `density` provided to `withMetadata`. - [#2952](https://github.com/lovell/sharp/pull/2952) - [@mbklein](https://github.com/mbklein) - -### v0.29.2 - 21st October 2021 - -* Add `timeout` function to limit processing time. - -* Ensure `sharp.versions` is populated from vendored libvips. - -* Remove animation properties from single page images. - [#2890](https://github.com/lovell/sharp/issues/2890) - -* Allow use of 'tif' to select TIFF output. - [#2893](https://github.com/lovell/sharp/pull/2893) - [@erf](https://github.com/erf) - -* Improve error message on Windows for version conflict. - [#2918](https://github.com/lovell/sharp/pull/2918) - [@dkrnl](https://github.com/dkrnl) - -* Throw error rather than exit when invalid binaries detected. - [#2931](https://github.com/lovell/sharp/issues/2931) - -### v0.29.1 - 7th September 2021 - -* Add `lightness` option to `modulate` operation. - [#2846](https://github.com/lovell/sharp/pull/2846) - -* Ensure correct PNG bitdepth is set based on number of colours. - [#2855](https://github.com/lovell/sharp/issues/2855) - -* Ensure background is always premultiplied when compositing. - [#2858](https://github.com/lovell/sharp/issues/2858) - -* Ensure images with P3 profiles retain full gamut. - [#2862](https://github.com/lovell/sharp/issues/2862) - -* Add support for libvips compiled with OpenJPEG. - [#2868](https://github.com/lovell/sharp/pull/2868) - -* Remove unsupported animation properties from AVIF output. - [#2870](https://github.com/lovell/sharp/issues/2870) - -* Resolve paths before comparing input/output filenames. - [#2878](https://github.com/lovell/sharp/pull/2878) - [@rexxars](https://github.com/rexxars) - -* Allow use of speed 9 (fastest) for HEIF encoding. - [#2879](https://github.com/lovell/sharp/pull/2879) - [@rexxars](https://github.com/rexxars) - -### v0.29.0 - 17th August 2021 - -* Drop support for Node.js 10, now requires Node.js >= 12.13.0. - -* Add `background` property to PNG and GIF image metadata. - -* Add `compression` property to HEIF image metadata. - [#2504](https://github.com/lovell/sharp/issues/2504) - -* AVIF encoding now defaults to `4:4:4` chroma subsampling. - [#2562](https://github.com/lovell/sharp/issues/2562) - -* Allow multiple platform-arch binaries in same `node_modules` installation tree. - [#2575](https://github.com/lovell/sharp/issues/2575) - -* Default to single-channel `b-w` space when `extractChannel` is used. - [#2658](https://github.com/lovell/sharp/issues/2658) - -* Allow installation directory to contain spaces (regression in v0.26.0). - [#2777](https://github.com/lovell/sharp/issues/2777) - -* Add `pipelineColourspace` operator to set the processing space. - [#2704](https://github.com/lovell/sharp/pull/2704) - [@Daiz](https://github.com/Daiz) - -* Allow bit depth to be set when using raw input and output. - [#2762](https://github.com/lovell/sharp/pull/2762) - [@mart-jansink](https://github.com/mart-jansink) - -* Allow `negate` to act only on non-alpha channels. - [#2808](https://github.com/lovell/sharp/pull/2808) - [@rexxars](https://github.com/rexxars) - -## v0.28 - *bijou* - -Requires libvips v8.10.6 - -### v0.28.3 - 24th May 2021 - -* Ensure presence of libvips, vendored or global, before invoking node-gyp. - -* Skip shrink-on-load for multi-page WebP. - [#2714](https://github.com/lovell/sharp/issues/2714) - -* Add contrast limiting adaptive histogram equalization (CLAHE) operator. - [#2726](https://github.com/lovell/sharp/pull/2726) - [@baparham](https://github.com/baparham) - -### v0.28.2 - 10th May 2021 - -* Allow `withMetadata` to set `density`. - [#967](https://github.com/lovell/sharp/issues/967) - -* Skip shrink-on-load where one dimension <4px. - [#2653](https://github.com/lovell/sharp/issues/2653) - -* Allow escaped proxy credentials. - [#2664](https://github.com/lovell/sharp/pull/2664) - [@msalettes](https://github.com/msalettes) - -* Add `premultiplied` flag for raw pixel data input. - [#2685](https://github.com/lovell/sharp/pull/2685) - [@mnutt](https://github.com/mnutt) - -* Detect empty input and throw a helpful error. - [#2687](https://github.com/lovell/sharp/pull/2687) - [@JakobJingleheimer](https://github.com/JakobJingleheimer) - -* Add install-time flag to skip version compatibility checks. - [#2692](https://github.com/lovell/sharp/pull/2692) - [@xemle](https://github.com/xemle) - -### v0.28.1 - 5th April 2021 - -* Ensure all installation errors are logged with a more obvious prefix. - -* Allow `withMetadata` to set and update EXIF metadata. - [#650](https://github.com/lovell/sharp/issues/650) - -* Add support for OME-TIFF Sub Image File Directories (subIFD). - [#2557](https://github.com/lovell/sharp/issues/2557) - -* Allow `ensureAlpha` to set the alpha transparency level. - [#2634](https://github.com/lovell/sharp/issues/2634) - -### v0.28.0 - 29th March 2021 - -* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause). - -* Prebuilt binaries limit AVIF support to the most common 8-bit depth. - -* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults. - -* Reduce the default PNG `compressionLevel` to the more commonly used 6. - -* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation. - -* Default missing edge properties of extend operation to zero. - [#2578](https://github.com/lovell/sharp/issues/2578) - -* Ensure composite does not clip top and left offsets. - [#2594](https://github.com/lovell/sharp/pull/2594) - [@SHG42](https://github.com/SHG42) - -* Improve error handling of network failure at install time. - [#2608](https://github.com/lovell/sharp/pull/2608) - [@abradley](https://github.com/abradley) - -* Ensure `@id` attribute can be set for IIIF tile-based output. - [#2612](https://github.com/lovell/sharp/issues/2612) - [@edsilv](https://github.com/edsilv) - -* Ensure composite replicates the correct number of tiles for centred gravities. - [#2626](https://github.com/lovell/sharp/issues/2626) - -## v0.27 - *avif* - -Requires libvips v8.10.5 - -### v0.27.2 - 22nd February 2021 - -* macOS: Prevent use of globally-installed ARM64 libvips with Rosetta x64 emulation. - [#2460](https://github.com/lovell/sharp/issues/2460) - -* Linux (musl): Prevent use of prebuilt linuxmusl-x64 binaries with musl >= 1.2.0. - [#2570](https://github.com/lovell/sharp/issues/2570) - -* Improve 16-bit grey+alpha support by using libvips' `has_alpha` detection. - [#2569](https://github.com/lovell/sharp/issues/2569) - -* Allow the use of non lower case extensions with `toFormat`. - [#2581](https://github.com/lovell/sharp/pull/2581) - [@florian-busch](https://github.com/florian-busch) - -* Allow use of `recomb` operation with single channel input. - [#2584](https://github.com/lovell/sharp/issues/2584) - -### v0.27.1 - 27th January 2021 - -* Ensure TIFF is cast when using float predictor. - [#2502](https://github.com/lovell/sharp/pull/2502) - [@randyridge](https://github.com/randyridge) - -* Add support for Uint8Array and Uint8ClampedArray input. - [#2511](https://github.com/lovell/sharp/pull/2511) - [@leon](https://github.com/leon) - -* Revert: ensure all platforms use fontconfig for font rendering. - [#2515](https://github.com/lovell/sharp/issues/2515) - -* Expose libvips gaussnoise operation to allow creation of Gaussian noise. - [#2527](https://github.com/lovell/sharp/pull/2527) - [@alza54](https://github.com/alza54) - -### v0.27.0 - 22nd December 2020 - -* Add support for AVIF to prebuilt binaries. - -* Remove experimental status from `heif` output, defaults are now AVIF-centric. - -* Allow negative top/left offsets for composite operation. - [#2391](https://github.com/lovell/sharp/pull/2391) - [@CurosMJ](https://github.com/CurosMJ) - -* Ensure all platforms use fontconfig for font rendering. - [#2399](https://github.com/lovell/sharp/issues/2399) - -## v0.26 - *zoom* - -Requires libvips v8.10.0 - -### v0.26.3 - 16th November 2020 - -* Expose libvips' affine operation. - [#2336](https://github.com/lovell/sharp/pull/2336) - [@guillevc](https://github.com/guillevc) - -* Fallback to tar.gz for prebuilt libvips when Brotli not available. - [#2412](https://github.com/lovell/sharp/pull/2412) - [@ascorbic](https://github.com/ascorbic) - -### v0.26.2 - 14th October 2020 - -* Add support for EXR input. Requires libvips compiled with OpenEXR. - [#698](https://github.com/lovell/sharp/issues/698) - -* Ensure support for yarn v2. - [#2379](https://github.com/lovell/sharp/pull/2379) - [@jalovatt](https://github.com/jalovatt) - -* Add centre/center option to tile-based output. - [#2397](https://github.com/lovell/sharp/pull/2397) - [@beig](https://github.com/beig) - -### v0.26.1 - 20th September 2020 - -* Ensure correct pageHeight when verifying multi-page image dimensions. - [#2343](https://github.com/lovell/sharp/pull/2343) - [@derom](https://github.com/derom) - -* Allow input density range up to 100000 DPI. - [#2348](https://github.com/lovell/sharp/pull/2348) - [@stefanprobst](https://github.com/stefanprobst) - -* Ensure animation-related properties can be set for Stream-based input. - [#2369](https://github.com/lovell/sharp/pull/2369) - [@AcrylicShrimp](https://github.com/AcrylicShrimp) - -* Ensure `stats` can be calculated for 1x1 input. - [#2372](https://github.com/lovell/sharp/issues/2372) - -* Ensure animated GIF output is optimised. - [#2376](https://github.com/lovell/sharp/issues/2376) - -### v0.26.0 - 25th August 2020 - -* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+. - -* TIFF output `squash` is replaced by `bitdepth` to reduce to 1, 2 or 4 bit. - -* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`. - -* Add most `dominant` colour to image `stats`. - [#640](https://github.com/lovell/sharp/issues/640) - -* Add support for animated GIF (requires \*magick) and WebP output. - [#2012](https://github.com/lovell/sharp/pull/2012) - [@deftomat](https://github.com/deftomat) - -* Add support for libvips ImageMagick v7 loaders. - [#2258](https://github.com/lovell/sharp/pull/2258) - [@vouillon](https://github.com/vouillon) - -* Allow multi-page input via \*magick. - [#2259](https://github.com/lovell/sharp/pull/2259) - [@vouillon](https://github.com/vouillon) - -* Add support to `withMetadata` for custom ICC profile. - [#2271](https://github.com/lovell/sharp/pull/2271) - [@roborourke](https://github.com/roborourke) - -* Ensure prebuilt binaries for ARM default to v7 when using Electron. - [#2292](https://github.com/lovell/sharp/pull/2292) - [@diegodev3](https://github.com/diegodev3) - -## v0.25 - *yield* - -Requires libvips v8.9.1 - -### v0.25.4 - 12th June 2020 - -* Allow libvips binary location override where version is appended. - [#2217](https://github.com/lovell/sharp/pull/2217) - [@malice00](https://github.com/malice00) - -* Enable PNG palette when setting quality, colours, colors or dither. - [#2226](https://github.com/lovell/sharp/pull/2226) - [@romaleev](https://github.com/romaleev) - -* Add `level` constructor option to use a specific level of a multi-level image. - Expose `levels` metadata for multi-level images. - [#2222](https://github.com/lovell/sharp/issues/2222) - -* Add support for named `alpha` channel to `extractChannel` operation. - [#2138](https://github.com/lovell/sharp/issues/2138) - -* Add experimental `sharpness` calculation to `stats()` response. - [#2251](https://github.com/lovell/sharp/issues/2251) - -* Emit `warning` event for non-critical processing problems. - [#2032](https://github.com/lovell/sharp/issues/2032) - -### v0.25.3 - 17th May 2020 - -* Ensure libvips is initialised only once, improves worker thread safety. - [#2143](https://github.com/lovell/sharp/issues/2143) - -* Ensure npm platform flag is respected when copying DLLs. - [#2188](https://github.com/lovell/sharp/pull/2188) - [@dimadeveatii](https://github.com/dimadeveatii) - -* Allow SVG input with large inline images to be parsed. - [#2195](https://github.com/lovell/sharp/issues/2195) - -### v0.25.2 - 20th March 2020 - -* Provide prebuilt binaries for Linux ARM64v8. - -* Add IIIF layout support to tile-based output. - [#2098](https://github.com/lovell/sharp/pull/2098) - [@edsilv](https://github.com/edsilv) - -* Ensure input options are consistently and correctly detected. - [#2118](https://github.com/lovell/sharp/issues/2118) - -* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives. - [#2119](https://github.com/lovell/sharp/issues/2119) - -* Ensure AsyncWorker options are persisted. - [#2130](https://github.com/lovell/sharp/issues/2130) - -### v0.25.1 - 7th March 2020 - -* Ensure prebuilt binaries are fetched based on N-API version. - [#2117](https://github.com/lovell/sharp/issues/2117) - -### v0.25.0 - 7th March 2020 - -* Remove `limitInputPixels` and `sequentialRead` previously deprecated in v0.24.0. - -* Migrate internals to N-API. - [#1282](https://github.com/lovell/sharp/issues/1282) - -* Add support for 32-bit Windows. - [#2088](https://github.com/lovell/sharp/issues/2088) - -* Ensure correct ordering of rotate-then-trim operations. - [#2087](https://github.com/lovell/sharp/issues/2087) - -* Ensure composite accepts `limitInputPixels` and `sequentialRead` input options. - [#2099](https://github.com/lovell/sharp/issues/2099) - -## v0.24 - "*wit*" - -Requires libvips v8.9.0. - -### v0.24.1 - 15th February 2020 - -* Prevent use of sequentialRead for EXIF-based rotate operation. - [#2042](https://github.com/lovell/sharp/issues/2042) - -* Ensure RGBA LZW TIFF returns correct channel count. - [#2064](https://github.com/lovell/sharp/issues/2064) - -### v0.24.0 - 16th January 2020 - -* Drop support for Node.js 8. - [#1910](https://github.com/lovell/sharp/issues/1910) - -* Drop support for undefined input where options also provided. - [#1768](https://github.com/lovell/sharp/issues/1768) - -* Move `limitInputPixels` and `sequentialRead` to input options, deprecating functions of the same name. - -* Expose `delay` and `loop` metadata for animated images. - [#1905](https://github.com/lovell/sharp/issues/1905) - -* Ensure correct colour output for 16-bit, 2-channel PNG input with ICC profile. - [#2013](https://github.com/lovell/sharp/issues/2013) - -* Prevent use of sequentialRead for rotate operations. - [#2016](https://github.com/lovell/sharp/issues/2016) - -* Correctly bind max width and height values when using withoutEnlargement. - [#2024](https://github.com/lovell/sharp/pull/2024) - [@BrychanOdlum](https://github.com/BrychanOdlum) - -* Add support for input with 16-bit RGB profile. - [#2037](https://github.com/lovell/sharp/issues/2037) - -## v0.23 - "*vision*" - -Requires libvips v8.8.1. - -### v0.23.4 - 5th December 2019 - -* Handle zero-length Buffer objects when using Node.js v13.2.0+. - -* Expose raw TIFFTAG_PHOTOSHOP metadata. - [#1600](https://github.com/lovell/sharp/issues/1600) - -* Improve thread safety by using copy-on-write when updating metadata. - [#1986](https://github.com/lovell/sharp/issues/1986) - -### v0.23.3 - 17th November 2019 - -* Ensure `trim` operation supports images contained in the alpha channel. - [#1597](https://github.com/lovell/sharp/issues/1597) - -* Ensure tile `overlap` option works as expected. - [#1921](https://github.com/lovell/sharp/pull/1921) - [@rustyguts](https://github.com/rustyguts) - -* Allow compilation on FreeBSD and variants (broken since v0.23.0) - [#1952](https://github.com/lovell/sharp/pull/1952) - [@pouya-eghbali](https://github.com/pouya-eghbali) - -* Ensure `modulate` and other colour-based operations can co-exist. - [#1958](https://github.com/lovell/sharp/issues/1958) - -### v0.23.2 - 28th October 2019 - -* Add `background` option to tile output operation. - [#1924](https://github.com/lovell/sharp/pull/1924) - [@neave](https://github.com/neave) - -* Add support for Node.js 13. - [#1932](https://github.com/lovell/sharp/pull/1932) - [@MayhemYDG](https://github.com/MayhemYDG) - -### v0.23.1 - 26th September 2019 - -* Ensure `sharp.format.vips` is present and correct (filesystem only). - [#1813](https://github.com/lovell/sharp/issues/1813) - -* Ensure invalid `width` and `height` provided as options to `resize` throw. - [#1817](https://github.com/lovell/sharp/issues/1817) - -* Allow use of 'heic' and 'heif' identifiers with `toFormat`. - [#1834](https://github.com/lovell/sharp/pull/1834) - [@jaubourg](https://github.com/jaubourg) - -* Add `premultiplied` option to `composite` operation. - [#1835](https://github.com/lovell/sharp/pull/1835) - [@Andargor](https://github.com/Andargor) - -* Allow instance reuse with differing `toBuffer` options. - [#1860](https://github.com/lovell/sharp/pull/1860) - [@RaboliotTheGrey](https://github.com/RaboliotTheGrey) - -* Ensure image is at least 3x3 pixels before attempting trim operation. - -### v0.23.0 - 29th July 2019 - -* Remove `overlayWith` previously deprecated in v0.22.0. - -* Add experimental support for HEIF images. Requires libvips compiled with libheif. - [#1105](https://github.com/lovell/sharp/issues/1105) - -* Expose libwebp `smartSubsample` and `reductionEffort` options. - [#1545](https://github.com/lovell/sharp/issues/1545) - -* Add experimental support for Worker Threads. - [#1558](https://github.com/lovell/sharp/issues/1558) - -* Use libvips' built-in CMYK and sRGB profiles when required. - [#1619](https://github.com/lovell/sharp/issues/1619) - -* Drop support for Node.js versions 6 and 11. - [#1674](https://github.com/lovell/sharp/issues/1674) - -* Expose `skipBlanks` option for tile-based output. - [#1687](https://github.com/lovell/sharp/pull/1687) - [@RaboliotTheGrey](https://github.com/RaboliotTheGrey) - -* Allow use of `failOnError` option with Stream-based input. - [#1691](https://github.com/lovell/sharp/issues/1691) - -* Fix rotate/extract ordering for non-90 angles. - [#1755](https://github.com/lovell/sharp/pull/1755) - [@iovdin](https://github.com/iovdin) - -## v0.22 - "*uptake*" - -Requires libvips v8.7.4. - -### v0.22.1 - 25th April 2019 - -* Add `modulate` operation for brightness, saturation and hue. - [#1601](https://github.com/lovell/sharp/pull/1601) - [@Goues](https://github.com/Goues) - -* Improve help messaging should `require("sharp")` fail. - [#1638](https://github.com/lovell/sharp/pull/1638) - [@sidharthachatterjee](https://github.com/sidharthachatterjee) - -* Add support for Node 12. - [#1668](https://github.com/lovell/sharp/issues/1668) - -### v0.22.0 - 18th March 2019 - -* Remove functions previously deprecated in v0.21.0: - `background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. - -* Add `composite` operation supporting multiple images and blend modes; deprecate `overlayWith`. - [#728](https://github.com/lovell/sharp/issues/728) - -* Add support for `pages` input option for multi-page input. - [#1566](https://github.com/lovell/sharp/issues/1566) - -* Allow Stream-based input of raw pixel data. - [#1579](https://github.com/lovell/sharp/issues/1579) - -* Add support for `page` input option to GIF and PDF. - [#1595](https://github.com/lovell/sharp/pull/1595) - [@ramiel](https://github.com/ramiel) - -## v0.21 - "*teeth*" - -Requires libvips v8.7.0. - -### v0.21.3 - 19th January 2019 - -* Input image decoding now fails fast, set `failOnError` to change this behaviour. - -* Failed filesystem-based input now separates missing file and invalid format errors. - [#1542](https://github.com/lovell/sharp/issues/1542) - -### v0.21.2 - 13th January 2019 - -* Ensure all metadata is removed from PNG output unless `withMetadata` used. - -* Ensure shortest edge is at least one pixel after resizing. - [#1003](https://github.com/lovell/sharp/issues/1003) - -* Add `ensureAlpha` operation to add an alpha channel, if missing. - [#1153](https://github.com/lovell/sharp/issues/1153) - -* Expose `pages` and `pageHeight` metadata for multi-page input images. - [#1205](https://github.com/lovell/sharp/issues/1205) - -* Expose PNG output options requiring libimagequant. - [#1484](https://github.com/lovell/sharp/issues/1484) - -* Expose underlying error message for invalid input. - [#1505](https://github.com/lovell/sharp/issues/1505) - -* Prevent mutatation of options passed to `jpeg`. - [#1516](https://github.com/lovell/sharp/issues/1516) - -* Ensure forced output format applied correctly when output chaining. - [#1528](https://github.com/lovell/sharp/issues/1528) - -### v0.21.1 - 7th December 2018 - -* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`. - [#1422](https://github.com/lovell/sharp/pull/1422) - [@SethWen](https://github.com/SethWen) - -* Ensure `channel` metadata is correct for raw, greyscale output. - [#1425](https://github.com/lovell/sharp/issues/1425) - -* Add support for the "mitchell" kernel for image reductions. - [#1438](https://github.com/lovell/sharp/pull/1438) - [@Daiz](https://github.com/Daiz) - -* Allow separate parameters for gamma encoding and decoding. - [#1439](https://github.com/lovell/sharp/pull/1439) - [@Daiz](https://github.com/Daiz) - -* Build prototype with `Object.assign` to allow minification. - [#1475](https://github.com/lovell/sharp/pull/1475) - [@jaubourg](https://github.com/jaubourg) - -* Expose libvips' recombination matrix operation. - [#1477](https://github.com/lovell/sharp/pull/1477) - [@fromkeith](https://github.com/fromkeith) - -* Expose libvips' pyramid/tile options for TIFF output. - [#1483](https://github.com/lovell/sharp/pull/1483) - [@mbklein](https://github.com/mbklein) - -### v0.21.0 - 4th October 2018 - -* Deprecate the following resize-related functions: - `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. - Access to these is now via options passed to the `resize` function. - For example: - `embed('north')` is now `resize(width, height, { fit: 'contain', position: 'north' })`, - `crop('attention')` is now `resize(width, height, { fit: 'cover', position: 'attention' })`, - `max().withoutEnlargement()` is now `resize(width, height, { fit: 'inside', withoutEnlargement: true })`. - [#1135](https://github.com/lovell/sharp/issues/1135) - -* Deprecate the `background` function. - Per-operation `background` options added to `resize`, `extend` and `flatten` operations. - [#1392](https://github.com/lovell/sharp/issues/1392) - -* Add `size` to `metadata` response (Stream and Buffer input only). - [#695](https://github.com/lovell/sharp/issues/695) - -* Switch from custom trim operation to `vips_find_trim`. - [#914](https://github.com/lovell/sharp/issues/914) - -* Add `chromaSubsampling` and `isProgressive` properties to `metadata` response. - [#1186](https://github.com/lovell/sharp/issues/1186) - -* Drop Node 4 support. - [#1212](https://github.com/lovell/sharp/issues/1212) - -* Enable SIMD convolution by default. - [#1213](https://github.com/lovell/sharp/issues/1213) - -* Add experimental prebuilt binaries for musl-based Linux. - [#1379](https://github.com/lovell/sharp/issues/1379) - -* Add support for arbitrary rotation angle via vips_rotate. - [#1385](https://github.com/lovell/sharp/pull/1385) - [@freezy](https://github.com/freezy) - -## v0.20 - "*prebuild*" - -Requires libvips v8.6.1. - -### v0.20.8 - 5th September 2018 - -* Avoid race conditions when creating directories during installation. - [#1358](https://github.com/lovell/sharp/pull/1358) - [@ajhool](https://github.com/ajhool) - -* Accept floating point values for input density parameter. - [#1362](https://github.com/lovell/sharp/pull/1362) - [@aeirola](https://github.com/aeirola) - -### v0.20.7 - 21st August 2018 - -* Use copy+unlink if rename operation fails during installation. - [#1345](https://github.com/lovell/sharp/issues/1345) - -### v0.20.6 - 20th August 2018 - -* Add removeAlpha operation to remove alpha channel, if any. - [#1248](https://github.com/lovell/sharp/issues/1248) - -* Expose mozjpeg quant_table flag. - [#1285](https://github.com/lovell/sharp/pull/1285) - [@rexxars](https://github.com/rexxars) - -* Allow full WebP alphaQuality range of 0-100. - [#1290](https://github.com/lovell/sharp/pull/1290) - [@sylvaindumont](https://github.com/sylvaindumont) - -* Cache libvips binaries to reduce re-install time. - [#1301](https://github.com/lovell/sharp/issues/1301) - -* Ensure vendor platform mismatch throws error at install time. - [#1303](https://github.com/lovell/sharp/issues/1303) - -* Improve install time error messages for FreeBSD users. - [#1310](https://github.com/lovell/sharp/issues/1310) - -* Ensure extractChannel works with 16-bit images. - [#1330](https://github.com/lovell/sharp/issues/1330) - -* Expose depth option for tile-based output. - [#1342](https://github.com/lovell/sharp/pull/1342) - [@alundavies](https://github.com/alundavies) - -* Add experimental entropy field to stats response. - -### v0.20.5 - 27th June 2018 - -* Expose libjpeg optimize_coding flag. - [#1265](https://github.com/lovell/sharp/pull/1265) - [@tomlokhorst](https://github.com/tomlokhorst) - -### v0.20.4 - 20th June 2018 - -* Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation. - [#1241](https://github.com/lovell/sharp/issues/1241) - [@anahit42](https://github.com/anahit42) - -* Ensure extractChannel sets correct single-channel colour space interpretation. - [#1257](https://github.com/lovell/sharp/issues/1257) - [@jeremychone](https://github.com/jeremychone) - -### v0.20.3 - 29th May 2018 - -* Fix tint operation by ensuring LAB interpretation and allowing negative values. - [#1235](https://github.com/lovell/sharp/issues/1235) - [@wezside](https://github.com/wezside) - -### v0.20.2 - 28th April 2018 - -* Add tint operation to set image chroma. - [#825](https://github.com/lovell/sharp/pull/825) - [@rikh42](https://github.com/rikh42) - -* Add environment variable to ignore globally-installed libvips. - [#1165](https://github.com/lovell/sharp/pull/1165) - [@oncletom](https://github.com/oncletom) - -* Add support for page selection with multi-page input (GIF/TIFF). - [#1204](https://github.com/lovell/sharp/pull/1204) - [@woolite64](https://github.com/woolite64) - -* Add support for Group4 (CCITTFAX4) compression with TIFF output. - [#1208](https://github.com/lovell/sharp/pull/1208) - [@woolite64](https://github.com/woolite64) - -### v0.20.1 - 17th March 2018 - -* Improve installation experience when a globally-installed libvips below the minimum required version is found. - [#1148](https://github.com/lovell/sharp/issues/1148) - -* Prevent smartcrop error when cumulative rounding is below target size. - [#1154](https://github.com/lovell/sharp/issues/1154) - [@ralrom](https://github.com/ralrom) - -* Expose libvips' median filter operation. - [#1161](https://github.com/lovell/sharp/pull/1161) - [@BiancoA](https://github.com/BiancoA) - -### v0.20.0 - 5th March 2018 - -* Add support for prebuilt sharp binaries on common platforms. - [#186](https://github.com/lovell/sharp/issues/186) - -## v0.19 - "*suit*" - -Requires libvips v8.6.1. - -### v0.19.1 - 24th February 2018 - -* Expose libvips' linear transform feature. - [#1024](https://github.com/lovell/sharp/pull/1024) - [@3epnm](https://github.com/3epnm) - -* Expose angle option for tile-based output. - [#1121](https://github.com/lovell/sharp/pull/1121) - [@BiancoA](https://github.com/BiancoA) - -* Prevent crop operation when image already at or below target dimensions. - [#1134](https://github.com/lovell/sharp/issues/1134) - [@pieh](https://github.com/pieh) - -### v0.19.0 - 11th January 2018 - -* Expose offset coordinates of strategy-based crop. - [#868](https://github.com/lovell/sharp/issues/868) - [@mirohristov-com](https://github.com/mirohristov-com) - -* PNG output now defaults to adaptiveFiltering=false, compressionLevel=9 - [#872](https://github.com/lovell/sharp/issues/872) - [@wmertens](https://github.com/wmertens) - -* Add stats feature for pixel-derived image statistics. - [#915](https://github.com/lovell/sharp/pull/915) - [@rnanwani](https://github.com/rnanwani) - -* Add failOnError option to fail-fast on bad input image data. - [#976](https://github.com/lovell/sharp/pull/976) - [@mceachen](https://github.com/mceachen) - -* Resize: switch to libvips' implementation, make fastShrinkOnLoad optional, remove interpolator and centreSampling options. - [#977](https://github.com/lovell/sharp/pull/977) - [@jardakotesovec](https://github.com/jardakotesovec) - -* Attach finish event listener to a clone only for Stream-based input. - [#995](https://github.com/lovell/sharp/issues/995) - [@whmountains](https://github.com/whmountains) - -* Add tilecache before smartcrop to avoid over-computation of previous operations. - [#1028](https://github.com/lovell/sharp/issues/1028) - [@coffeebite](https://github.com/coffeebite) - -* Prevent toFile extension taking precedence over requested format. - [#1037](https://github.com/lovell/sharp/issues/1037) - [@tomgallagher](https://github.com/tomgallagher) - -* Add support for gravity option to existing embed feature. - [#1038](https://github.com/lovell/sharp/pull/1038) - [@AzureByte](https://github.com/AzureByte) - -* Expose IPTC and XMP metadata when available. - [#1079](https://github.com/lovell/sharp/pull/1079) - [@oaleynik](https://github.com/oaleynik) - -* TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour. - -## v0.18 - "*ridge*" - -Requires libvips v8.5.5. - -### v0.18.4 - 18th September 2017 - -* Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC. - [#950](https://github.com/lovell/sharp/issues/950) - [@lfdoherty](https://github.com/lfdoherty) - -### v0.18.3 - 13th September 2017 - -* Skip shrink-on-load when trimming. - [#888](https://github.com/lovell/sharp/pull/888) - [@kleisauke](https://github.com/kleisauke) - -* Migrate from got to simple-get for basic auth support. - [#945](https://github.com/lovell/sharp/pull/945) - [@pbomb](https://github.com/pbomb) - -### v0.18.2 - 1st July 2017 - -* Expose libvips' xres and yres properties for TIFF output. - [#828](https://github.com/lovell/sharp/pull/828) - [@YvesBos](https://github.com/YvesBos) - -* Ensure flip and flop operations work with auto-rotate. - [#837](https://github.com/lovell/sharp/issues/837) - [@rexxars](https://github.com/rexxars) - -* Allow binary download URL override via SHARP_DIST_BASE_URL env variable. - [#841](https://github.com/lovell/sharp/issues/841) - -* Add support for Solus Linux. - [#857](https://github.com/lovell/sharp/pull/857) - [@ekremkaraca](https://github.com/ekremkaraca) - -### v0.18.1 - 30th May 2017 - -* Remove regression from #781 that could cause incorrect shrink calculation. - [#831](https://github.com/lovell/sharp/issues/831) - [@suprMax](https://github.com/suprMax) - -### v0.18.0 - 30th May 2017 - -* Remove the previously-deprecated output format "option" functions: - quality, progressive, compressionLevel, withoutAdaptiveFiltering, - withoutChromaSubsampling, trellisQuantisation, trellisQuantization, - overshootDeringing, optimiseScans and optimizeScans. - -* Ensure maximum output dimensions are based on the format to be used. - [#176](https://github.com/lovell/sharp/issues/176) - [@stephanebachelier](https://github.com/stephanebachelier) - -* Avoid costly (un)premultiply when using overlayWith without alpha channel. - [#573](https://github.com/lovell/sharp/issues/573) - [@strarsis](https://github.com/strarsis) - -* Include pixel depth (e.g. "uchar") when reading metadata. - [#577](https://github.com/lovell/sharp/issues/577) - [@moedusa](https://github.com/moedusa) - -* Add support for Buffer and Stream-based TIFF output. - [#587](https://github.com/lovell/sharp/issues/587) - [@strarsis](https://github.com/strarsis) - -* Expose warnings from libvips via NODE_DEBUG=sharp environment variable. - [#607](https://github.com/lovell/sharp/issues/607) - [@puzrin](https://github.com/puzrin) - -* Switch to the libvips implementation of "attention" and "entropy" crop strategies. - [#727](https://github.com/lovell/sharp/issues/727) - -* Improve performance and accuracy of nearest neighbour integral upsampling. - [#752](https://github.com/lovell/sharp/issues/752) - [@MrIbby](https://github.com/MrIbby) - -* Constructor single argument API: allow plain object, reject null/undefined. - [#768](https://github.com/lovell/sharp/issues/768) - [@kub1x](https://github.com/kub1x) - -* Ensure ARM64 pre-built binaries use correct C++11 ABI version. - [#772](https://github.com/lovell/sharp/issues/772) - [@ajiratech2](https://github.com/ajiratech2) - -* Prevent aliasing by using dynamic values for shrink(-on-load). - [#781](https://github.com/lovell/sharp/issues/781) - [@kleisauke](https://github.com/kleisauke) - -* Expose libvips' "squash" parameter to enable 1-bit TIFF output. - [#783](https://github.com/lovell/sharp/pull/783) - [@YvesBos](https://github.com/YvesBos) - -* Add support for rotation using any multiple of +/-90 degrees. - [#791](https://github.com/lovell/sharp/pull/791) - [@ncoden](https://github.com/ncoden) - -* Add "jpg" alias to toFormat as shortened form of "jpeg". - [#814](https://github.com/lovell/sharp/pull/814) - [@jingsam](https://github.com/jingsam) - -## v0.17 - "*quill*" - -Requires libvips v8.4.2. - -### v0.17.3 - 1st April 2017 - -* Allow toBuffer to optionally resolve a Promise with both info and data. - [#143](https://github.com/lovell/sharp/issues/143) - [@salzhrani](https://github.com/salzhrani) - -* Create blank image of given width, height, channels and background. - [#470](https://github.com/lovell/sharp/issues/470) - [@pjarts](https://github.com/pjarts) - -* Add support for the "nearest" kernel for image reductions. - [#732](https://github.com/lovell/sharp/pull/732) - [@alice0meta](https://github.com/alice0meta) - -* Add support for TIFF compression and predictor options. - [#738](https://github.com/lovell/sharp/pull/738) - [@kristojorg](https://github.com/kristojorg) - -### v0.17.2 - 11th February 2017 - -* Ensure Readable side of Stream can start flowing after Writable side has finished. - [#671](https://github.com/lovell/sharp/issues/671) - [@danhaller](https://github.com/danhaller) - -* Expose WebP alpha quality, lossless and near-lossless output options. - [#685](https://github.com/lovell/sharp/pull/685) - [@rnanwani](https://github.com/rnanwani) - -### v0.17.1 - 15th January 2017 - -* Improve error messages for invalid parameters. - [@spikeon](https://github.com/spikeon) - [#644](https://github.com/lovell/sharp/pull/644) - -* Simplify expression for finding vips-cpp libdir. - [#656](https://github.com/lovell/sharp/pull/656) - -* Allow HTTPS-over-HTTP proxy when downloading pre-compiled dependencies. - [@wangzhiwei1888](https://github.com/wangzhiwei1888) - [#679](https://github.com/lovell/sharp/issues/679) - -### v0.17.0 - 11th December 2016 - -* Drop support for versions of Node prior to v4. - -* Deprecate the following output format "option" functions: - quality, progressive, compressionLevel, withoutAdaptiveFiltering, - withoutChromaSubsampling, trellisQuantisation, trellisQuantization, - overshootDeringing, optimiseScans and optimizeScans. - Access to these is now via output format functions, for example `quality(n)` - is now `jpeg({quality: n})` and/or `webp({quality: n})`. - -* Autoconvert GIF and SVG input to PNG output if no other format is specified. - -* Expose libvips' "centre" resize option to mimic \*magick's +0.5px convention. - [#568](https://github.com/lovell/sharp/issues/568) - -* Ensure support for embedded base64 PNG and JPEG images within an SVG. - [#601](https://github.com/lovell/sharp/issues/601) - [@dynamite-ready](https://github.com/dynamite-ready) - -* Ensure premultiply operation occurs before box filter shrink. - [#605](https://github.com/lovell/sharp/issues/605) - [@CmdrShepardsPie](https://github.com/CmdrShepardsPie) - [@teroparvinen](https://github.com/teroparvinen) - -* Add support for PNG and WebP tile-based output formats (in addition to JPEG). - [#622](https://github.com/lovell/sharp/pull/622) - [@ppaskaris](https://github.com/ppaskaris) - -* Allow use of extend with greyscale input. - [#623](https://github.com/lovell/sharp/pull/623) - [@ppaskaris](https://github.com/ppaskaris) - -* Allow non-RGB input to embed/extend onto background with an alpha channel. - [#646](https://github.com/lovell/sharp/issues/646) - [@DaGaMs](https://github.com/DaGaMs) - -## v0.16 - "*pencil*" - -Requires libvips v8.3.3 - -### v0.16.2 - 22nd October 2016 - -* Restrict readelf usage to Linux only when detecting global libvips version. - [#602](https://github.com/lovell/sharp/issues/602) - [@caoko](https://github.com/caoko) - -### v0.16.1 - 13th October 2016 - -* C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag. - -* Add experimental 'attention' crop strategy. - [#295](https://github.com/lovell/sharp/issues/295) - -* Include .node extension for Meteor's require() implementation. - [#537](https://github.com/lovell/sharp/issues/537) - [@isjackwild](https://github.com/isjackwild) - -* Ensure convolution kernel scale is clamped to a minimum value of 1. - [#561](https://github.com/lovell/sharp/issues/561) - [@abagshaw](https://github.com/abagshaw) - -* Correct calculation of y-axis placement when overlaying image at a fixed point. - [#566](https://github.com/lovell/sharp/issues/566) - [@Nateowami](https://github.com/Nateowami) - -### v0.16.0 - 18th August 2016 - -* Add pre-compiled libvips for OS X, ARMv7 and ARMv8. - [#312](https://github.com/lovell/sharp/issues/312) - -* Ensure boolean, bandbool, extractChannel ops occur before sRGB conversion. - [#504](https://github.com/lovell/sharp/pull/504) - [@mhirsch](https://github.com/mhirsch) - -* Recalculate factors after WebP shrink-on-load to avoid round-to-zero errors. - [#508](https://github.com/lovell/sharp/issues/508) - [@asilvas](https://github.com/asilvas) - -* Prevent boolean errors during extract operation. - [#511](https://github.com/lovell/sharp/pull/511) - [@mhirsch](https://github.com/mhirsch) - -* Add joinChannel and toColourspace/toColorspace operations. - [#513](https://github.com/lovell/sharp/pull/513) - [@mhirsch](https://github.com/mhirsch) - -* Add support for raw pixel data with boolean and withOverlay operations. - [#516](https://github.com/lovell/sharp/pull/516) - [@mhirsch](https://github.com/mhirsch) - -* Prevent bandbool creating a single channel sRGB image. - [#519](https://github.com/lovell/sharp/pull/519) - [@mhirsch](https://github.com/mhirsch) - -* Ensure ICC profiles are removed from PNG output unless withMetadata used. - [#521](https://github.com/lovell/sharp/issues/521) - [@ChrisPinewood](https://github.com/ChrisPinewood) - -* Add alpha channels, if missing, to overlayWith images. - [#540](https://github.com/lovell/sharp/pull/540) - [@cmtt](https://github.com/cmtt) - -* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... }) - [#310](https://github.com/lovell/sharp/issues/310) - -## v0.15 - "*outfit*" - -Requires libvips v8.3.1 - -### v0.15.1 - 12th July 2016 - -* Concat Stream-based input in single operation for ~+3% perf and less GC. - [#429](https://github.com/lovell/sharp/issues/429) - [@papandreou](https://github.com/papandreou) - -* Add alpha channel, if required, before extend operation. - [#439](https://github.com/lovell/sharp/pull/439) - [@frulo](https://github.com/frulo) - -* Allow overlay image to be repeated across entire image via tile option. - [#443](https://github.com/lovell/sharp/pull/443) - [@lemnisk8](https://github.com/lemnisk8) - -* Add cutout option to overlayWith feature, applies only the alpha channel of the overlay image. - [#448](https://github.com/lovell/sharp/pull/448) - [@kleisauke](https://github.com/kleisauke) - -* Ensure scaling factors are calculated independently to prevent rounding errors. - [#452](https://github.com/lovell/sharp/issues/452) - [@puzrin](https://github.com/puzrin) - -* Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI. - [#456](https://github.com/lovell/sharp/pull/456) - [@kapouer](https://github.com/kapouer) - -* Add top/left offset support to overlayWith operation. - [#473](https://github.com/lovell/sharp/pull/473) - [@rnanwani](https://github.com/rnanwani) - -* Add convolve operation for kernel-based convolution. - [#479](https://github.com/lovell/sharp/pull/479) - [@mhirsch](https://github.com/mhirsch) - -* Add greyscale option to threshold operation for colourspace conversion control. - [#480](https://github.com/lovell/sharp/pull/480) - [@mhirsch](https://github.com/mhirsch) - -* Ensure ICC profiles are licenced for distribution. - [#486](https://github.com/lovell/sharp/issues/486) - [@kapouer](https://github.com/kapouer) - -* Allow images with an alpha channel to work with LAB-colourspace based sharpen. - [#490](https://github.com/lovell/sharp/issues/490) - [@jwagner](https://github.com/jwagner) - -* Add trim operation to remove "boring" edges. - [#492](https://github.com/lovell/sharp/pull/492) - [@kleisauke](https://github.com/kleisauke) - -* Add bandbool feature for channel-wise boolean operations. - [#496](https://github.com/lovell/sharp/pull/496) - [@mhirsch](https://github.com/mhirsch) - -* Add extractChannel operation to extract a channel from an image. - [#497](https://github.com/lovell/sharp/pull/497) - [@mhirsch](https://github.com/mhirsch) - -* Add ability to read and write native libvips .v files. - [#500](https://github.com/lovell/sharp/pull/500) - [@mhirsch](https://github.com/mhirsch) - -* Add boolean feature for bitwise image operations. - [#501](https://github.com/lovell/sharp/pull/501) - [@mhirsch](https://github.com/mhirsch) - -### v0.15.0 - 21st May 2016 - -* Use libvips' new Lanczos 3 kernel as default for image reduction. - Deprecate interpolateWith method, now provided as a resize option. - [#310](https://github.com/lovell/sharp/issues/310) - [@jcupitt](https://github.com/jcupitt) - -* Take advantage of libvips v8.3 features. - Add support for libvips' new GIF and SVG loaders. - Pre-built binaries now include giflib and librsvg, exclude *magick. - Use shrink-on-load for WebP input. - Break existing sharpen API to accept sigma and improve precision. - [#369](https://github.com/lovell/sharp/issues/369) - -* Remove unnecessary (un)premultiply operations when not resizing/compositing. - [#413](https://github.com/lovell/sharp/issues/413) - [@jardakotesovec](https://github.com/jardakotesovec) - -## v0.14 - "*needle*" - -Requires libvips v8.2.3 - -### v0.14.1 - 16th April 2016 - -* Allow removal of limitation on input pixel count via limitInputPixels. Use with care. - [#250](https://github.com/lovell/sharp/issues/250) - [#316](https://github.com/lovell/sharp/pull/316) - [@anandthakker](https://github.com/anandthakker) - [@kentongray](https://github.com/kentongray) - -* Use final output image for metadata passed to callback. - [#399](https://github.com/lovell/sharp/pull/399) - [@salzhrani](https://github.com/salzhrani) - -* Add support for writing tiled images to a zip container. - [#402](https://github.com/lovell/sharp/pull/402) - [@felixbuenemann](https://github.com/felixbuenemann) - -* Allow use of embed with 1 and 2 channel images. - [#411](https://github.com/lovell/sharp/issues/411) - [@janaz](https://github.com/janaz) - -* Improve Electron compatibility by allowing node-gyp rebuilds without npm. - [#412](https://github.com/lovell/sharp/issues/412) - [@nouh](https://github.com/nouh) - -### v0.14.0 - 2nd April 2016 - -* Add ability to extend (pad) the edges of an image. - [#128](https://github.com/lovell/sharp/issues/128) - [@blowsie](https://github.com/blowsie) - -* Add support for Zoomify and Google tile layouts. Breaks existing tile API. - [#223](https://github.com/lovell/sharp/issues/223) - [@bdunnette](https://github.com/bdunnette) - -* Improvements to overlayWith: differing sizes/formats, gravity, buffer input. - [#239](https://github.com/lovell/sharp/issues/239) - [@chrisriley](https://github.com/chrisriley) - -* Add entropy-based crop strategy to remove least interesting edges. - [#295](https://github.com/lovell/sharp/issues/295) - [@rightaway](https://github.com/rightaway) - -* Expose density metadata; set density of images from vector input. - [#338](https://github.com/lovell/sharp/issues/338) - [@lookfirst](https://github.com/lookfirst) - -* Emit post-processing 'info' event for Stream output. - [#367](https://github.com/lovell/sharp/issues/367) - [@salzhrani](https://github.com/salzhrani) - -* Ensure output image EXIF Orientation values are within 1-8 range. - [#385](https://github.com/lovell/sharp/pull/385) - [@jtobinisaniceguy](https://github.com/jtobinisaniceguy) - -* Ensure ratios are not swapped when rotating 90/270 and ignoring aspect. - [#387](https://github.com/lovell/sharp/issues/387) - [@kleisauke](https://github.com/kleisauke) - -* Remove deprecated style of calling extract API. Breaks calls using positional arguments. - [#276](https://github.com/lovell/sharp/issues/276) - -## v0.13 - "*mind*" - -Requires libvips v8.2.2 - -### v0.13.1 - 27th February 2016 - -* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0. - [#366](https://github.com/lovell/sharp/issues/366) - [@diegocsandrim](https://github.com/diegocsandrim) - -### v0.13.0 - 15th February 2016 - -* Improve vector image support by allowing control of density/DPI. - Switch pre-built libs from Imagemagick to Graphicsmagick. - [#110](https://github.com/lovell/sharp/issues/110) - [@bradisbell](https://github.com/bradisbell) - -* Add support for raw, uncompressed pixel Buffer/Stream input. - [#220](https://github.com/lovell/sharp/issues/220) - [@mikemorris](https://github.com/mikemorris) - -* Switch from libvips' C to C++ bindings, requires upgrade to v8.2.2. - [#299](https://github.com/lovell/sharp/issues/299) - -* Control number of open files in libvips' cache; breaks existing `cache` behaviour. - [#315](https://github.com/lovell/sharp/issues/315) - [@impomezia](https://github.com/impomezia) - -* Ensure 16-bit input images can be normalised and embedded onto transparent backgrounds. - [#339](https://github.com/lovell/sharp/issues/339) - [#340](https://github.com/lovell/sharp/issues/340) - [@janaz](https://github.com/janaz) - -* Ensure selected format takes precedence over any unknown output filename extension. - [#344](https://github.com/lovell/sharp/issues/344) - [@ubaltaci](https://github.com/ubaltaci) - -* Add support for libvips' PBM, PGM, PPM and FITS image format loaders. - [#347](https://github.com/lovell/sharp/issues/347) - [@oaleynik](https://github.com/oaleynik) - -* Ensure default crop gravity is center/centre. - [#351](https://github.com/lovell/sharp/pull/351) - [@joelmukuthu](https://github.com/joelmukuthu) - -* Improve support for musl libc systems e.g. Alpine Linux. - [#354](https://github.com/lovell/sharp/issues/354) - [#359](https://github.com/lovell/sharp/pull/359) - [@download13](https://github.com/download13) - [@wjordan](https://github.com/wjordan) - -* Small optimisation when reducing by an integral factor to favour shrink over affine. - -* Add support for gamma correction of images with an alpha channel. - -## v0.12 - "*look*" - -Requires libvips v8.2.0 - -### v0.12.2 - 16th January 2016 - -* Upgrade libvips to v8.2.0 for improved vips_shrink. - -* Add pre-compiled libvips for ARMv6+ CPUs. - -* Ensure 16-bit input images work with embed option. - [#325](https://github.com/lovell/sharp/issues/325) - [@janaz](https://github.com/janaz) - -* Allow compilation with gmake to provide FreeBSD support. - [#326](https://github.com/lovell/sharp/issues/326) - [@c0decafe](https://github.com/c0decafe) - -* Attempt to remove temporary file after installation. - [#331](https://github.com/lovell/sharp/issues/331) - [@dtoubelis](https://github.com/dtoubelis) - -### v0.12.1 - 12th December 2015 - -* Allow use of SIMD vector instructions (via liborc) to be toggled on/off. - [#172](https://github.com/lovell/sharp/issues/172) - [@bkw](https://github.com/bkw) - [@puzrin](https://github.com/puzrin) - -* Ensure embedded ICC profiles output with perceptual intent. - [#321](https://github.com/lovell/sharp/issues/321) - [@vlapo](https://github.com/vlapo) - -* Use the NPM-configured HTTPS proxy, if any, for binary downloads. - -### v0.12.0 - 23rd November 2015 - -* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows. - [#42](https://github.com/lovell/sharp/issues/42) - -* Take advantage of libvips v8.1.0+ features. - [#152](https://github.com/lovell/sharp/issues/152) - -* Add support for 64-bit Windows. Drop support for 32-bit Windows. - [#224](https://github.com/lovell/sharp/issues/224) - [@sabrehagen](https://github.com/sabrehagen) - -* Switch default interpolator to bicubic. - [#289](https://github.com/lovell/sharp/issues/289) - [@mahnunchik](https://github.com/mahnunchik) - -* Pre-extract rotatation should not swap width/height. - [#296](https://github.com/lovell/sharp/issues/296) - [@asilvas](https://github.com/asilvas) - -* Ensure 16-bit+alpha input images are (un)premultiplied correctly. - [#301](https://github.com/lovell/sharp/issues/301) - [@izaakschroeder](https://github.com/izaakschroeder) - -* Add `threshold` operation. - [#303](https://github.com/lovell/sharp/pull/303) - [@dacarley](https://github.com/dacarley) - -* Add `negate` operation. - [#306](https://github.com/lovell/sharp/pull/306) - [@dacarley](https://github.com/dacarley) - -* Support `options` Object with existing `extract` operation. - [#309](https://github.com/lovell/sharp/pull/309) - [@papandreou](https://github.com/papandreou) - -## v0.11 - "*knife*" - -### v0.11.4 - 5th November 2015 - -* Add corners, e.g. `northeast`, to existing `gravity` option. - [#291](https://github.com/lovell/sharp/pull/291) - [@brandonaaron](https://github.com/brandonaaron) - -* Ensure correct auto-rotation for EXIF Orientation values 2 and 4. - [#288](https://github.com/lovell/sharp/pull/288) - [@brandonaaron](https://github.com/brandonaaron) - -* Make static linking possible via `--runtime_link` install option. - [#287](https://github.com/lovell/sharp/pull/287) - [@vlapo](https://github.com/vlapo) - -### v0.11.3 - 8th September 2015 - -* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision. - [#263](https://github.com/lovell/sharp/pull/263) - [@chrisriley](https://github.com/chrisriley) - -### v0.11.2 - 28th August 2015 - -* Allow crop gravity to be provided as a String. - [#255](https://github.com/lovell/sharp/pull/255) - [@papandreou](https://github.com/papandreou) -* Add support for io.js v3 and Node v4. - [#246](https://github.com/lovell/sharp/issues/246) - -### v0.11.1 - 12th August 2015 - -* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled". - [#244](https://github.com/lovell/sharp/pull/244) - [@TheThing](https://github.com/TheThing) - -* Suppress gamma correction for input image with alpha transparency. - [#249](https://github.com/lovell/sharp/issues/249) - [@compeak](https://github.com/compeak) - -### v0.11.0 - 15th July 2015 - -* Allow alpha transparency compositing via new `overlayWith` method. - [#97](https://github.com/lovell/sharp/issues/97) - [@gasi](https://github.com/gasi) - -* Expose raw ICC profile data as a Buffer when using `metadata`. - [#129](https://github.com/lovell/sharp/issues/129) - [@homerjam](https://github.com/homerjam) - -* Allow image header updates via a parameter passed to existing `withMetadata` method. - Provide initial support for EXIF `Orientation` tag, - which if present is now removed when using `rotate`, `flip` or `flop`. - [#189](https://github.com/lovell/sharp/issues/189) - [@h2non](https://github.com/h2non) - -* Tighten constructor parameter checks. - [#221](https://github.com/lovell/sharp/issues/221) - [@mikemorris](https://github.com/mikemorris) - -* Allow one input Stream to be shared with two or more output Streams via new `clone` method. - [#235](https://github.com/lovell/sharp/issues/235) - [@jaubourg](https://github.com/jaubourg) - -* Use `round` instead of `floor` when auto-scaling dimensions to avoid floating-point rounding errors. - [#238](https://github.com/lovell/sharp/issues/238) - [@richardadjogah](https://github.com/richardadjogah) - -## v0.10 - "*judgment*" - -### v0.10.1 - 1st June 2015 - -* Allow embed of image with alpha transparency onto non-transparent background. - [#204](https://github.com/lovell/sharp/issues/204) - [@mikemliu](https://github.com/mikemliu) - -* Include C standard library for `atoi` as Xcode 6.3 appears to no longer do this. - [#228](https://github.com/lovell/sharp/issues/228) - [@doggan](https://github.com/doggan) - -### v0.10.0 - 23rd April 2015 - -* Add support for Windows (x86). - [#19](https://github.com/lovell/sharp/issues/19) - [@DullReferenceException](https://github.com/DullReferenceException) - [@itsananderson](https://github.com/itsananderson) - -* Add support for Openslide input and DeepZoom output. - [#146](https://github.com/lovell/sharp/issues/146) - [@mvictoras](https://github.com/mvictoras) - -* Allow arbitrary aspect ratios when resizing images via new `ignoreAspectRatio` method. - [#192](https://github.com/lovell/sharp/issues/192) - [@skedastik](https://github.com/skedastik) - -* Enhance output image contrast by stretching its luminance to cover the full dynamic range via new `normalize` method. - [#194](https://github.com/lovell/sharp/issues/194) - [@bkw](https://github.com/bkw) - [@codingforce](https://github.com/codingforce) diff --git a/docs/src/content/docs/changelog/_meta.yml b/docs/src/content/docs/changelog/_meta.yml new file mode 100644 index 000000000..5dd257241 --- /dev/null +++ b/docs/src/content/docs/changelog/_meta.yml @@ -0,0 +1 @@ +sort: reverse-slug diff --git a/docs/src/content/docs/changelog/v0.10.0.md b/docs/src/content/docs/changelog/v0.10.0.md new file mode 100644 index 000000000..aae2ca0ee --- /dev/null +++ b/docs/src/content/docs/changelog/v0.10.0.md @@ -0,0 +1,22 @@ +--- +title: v0.10.0 - 23rd April 2015 +slug: changelog/v0.10.0 +--- + +* Add support for Windows (x86). + [#19](https://github.com/lovell/sharp/issues/19) + [@DullReferenceException](https://github.com/DullReferenceException) + [@itsananderson](https://github.com/itsananderson) + +* Add support for Openslide input and DeepZoom output. + [#146](https://github.com/lovell/sharp/issues/146) + [@mvictoras](https://github.com/mvictoras) + +* Allow arbitrary aspect ratios when resizing images via new `ignoreAspectRatio` method. + [#192](https://github.com/lovell/sharp/issues/192) + [@skedastik](https://github.com/skedastik) + +* Enhance output image contrast by stretching its luminance to cover the full dynamic range via new `normalize` method. + [#194](https://github.com/lovell/sharp/issues/194) + [@bkw](https://github.com/bkw) + [@codingforce](https://github.com/codingforce) diff --git a/docs/src/content/docs/changelog/v0.10.1.md b/docs/src/content/docs/changelog/v0.10.1.md new file mode 100644 index 000000000..6f0336861 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.10.1.md @@ -0,0 +1,12 @@ +--- +title: v0.10.1 - 1st June 2015 +slug: changelog/v0.10.1 +--- + +* Allow embed of image with alpha transparency onto non-transparent background. + [#204](https://github.com/lovell/sharp/issues/204) + [@mikemliu](https://github.com/mikemliu) + +* Include C standard library for `atoi` as Xcode 6.3 appears to no longer do this. + [#228](https://github.com/lovell/sharp/issues/228) + [@doggan](https://github.com/doggan) diff --git a/docs/src/content/docs/changelog/v0.11.0.md b/docs/src/content/docs/changelog/v0.11.0.md new file mode 100644 index 000000000..2b07a93a5 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.11.0.md @@ -0,0 +1,30 @@ +--- +title: v0.11.0 - 15th July 2015 +slug: changelog/v0.11.0 +--- + +* Allow alpha transparency compositing via new `overlayWith` method. + [#97](https://github.com/lovell/sharp/issues/97) + [@gasi](https://github.com/gasi) + +* Expose raw ICC profile data as a Buffer when using `metadata`. + [#129](https://github.com/lovell/sharp/issues/129) + [@homerjam](https://github.com/homerjam) + +* Allow image header updates via a parameter passed to existing `withMetadata` method. + Provide initial support for EXIF `Orientation` tag, + which if present is now removed when using `rotate`, `flip` or `flop`. + [#189](https://github.com/lovell/sharp/issues/189) + [@h2non](https://github.com/h2non) + +* Tighten constructor parameter checks. + [#221](https://github.com/lovell/sharp/issues/221) + [@mikemorris](https://github.com/mikemorris) + +* Allow one input Stream to be shared with two or more output Streams via new `clone` method. + [#235](https://github.com/lovell/sharp/issues/235) + [@jaubourg](https://github.com/jaubourg) + +* Use `round` instead of `floor` when auto-scaling dimensions to avoid floating-point rounding errors. + [#238](https://github.com/lovell/sharp/issues/238) + [@richardadjogah](https://github.com/richardadjogah) diff --git a/docs/src/content/docs/changelog/v0.11.1.md b/docs/src/content/docs/changelog/v0.11.1.md new file mode 100644 index 000000000..44bdfc925 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.11.1.md @@ -0,0 +1,12 @@ +--- +title: v0.11.1 - 12th August 2015 +slug: changelog/v0.11.1 +--- + +* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled". + [#244](https://github.com/lovell/sharp/pull/244) + [@TheThing](https://github.com/TheThing) + +* Suppress gamma correction for input image with alpha transparency. + [#249](https://github.com/lovell/sharp/issues/249) + [@compeak](https://github.com/compeak) diff --git a/docs/src/content/docs/changelog/v0.11.2.md b/docs/src/content/docs/changelog/v0.11.2.md new file mode 100644 index 000000000..9990b05f7 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.11.2.md @@ -0,0 +1,10 @@ +--- +title: v0.11.2 - 28th August 2015 +slug: changelog/v0.11.2 +--- + +* Allow crop gravity to be provided as a String. + [#255](https://github.com/lovell/sharp/pull/255) + [@papandreou](https://github.com/papandreou) +* Add support for io.js v3 and Node v4. + [#246](https://github.com/lovell/sharp/issues/246) diff --git a/docs/src/content/docs/changelog/v0.11.3.md b/docs/src/content/docs/changelog/v0.11.3.md new file mode 100644 index 000000000..a062d1b8b --- /dev/null +++ b/docs/src/content/docs/changelog/v0.11.3.md @@ -0,0 +1,8 @@ +--- +title: v0.11.3 - 8th September 2015 +slug: changelog/v0.11.3 +--- + +* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision. + [#263](https://github.com/lovell/sharp/pull/263) + [@chrisriley](https://github.com/chrisriley) diff --git a/docs/src/content/docs/changelog/v0.11.4.md b/docs/src/content/docs/changelog/v0.11.4.md new file mode 100644 index 000000000..f8cba2cd6 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.11.4.md @@ -0,0 +1,16 @@ +--- +title: v0.11.4 - 5th November 2015 +slug: changelog/v0.11.4 +--- + +* Add corners, e.g. `northeast`, to existing `gravity` option. + [#291](https://github.com/lovell/sharp/pull/291) + [@brandonaaron](https://github.com/brandonaaron) + +* Ensure correct auto-rotation for EXIF Orientation values 2 and 4. + [#288](https://github.com/lovell/sharp/pull/288) + [@brandonaaron](https://github.com/brandonaaron) + +* Make static linking possible via `--runtime_link` install option. + [#287](https://github.com/lovell/sharp/pull/287) + [@vlapo](https://github.com/vlapo) diff --git a/docs/src/content/docs/changelog/v0.12.0.md b/docs/src/content/docs/changelog/v0.12.0.md new file mode 100644 index 000000000..c081be9de --- /dev/null +++ b/docs/src/content/docs/changelog/v0.12.0.md @@ -0,0 +1,38 @@ +--- +title: v0.12.0 - 23rd November 2015 +slug: changelog/v0.12.0 +--- + +* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows. + [#42](https://github.com/lovell/sharp/issues/42) + +* Take advantage of libvips v8.1.0+ features. + [#152](https://github.com/lovell/sharp/issues/152) + +* Add support for 64-bit Windows. Drop support for 32-bit Windows. + [#224](https://github.com/lovell/sharp/issues/224) + [@sabrehagen](https://github.com/sabrehagen) + +* Switch default interpolator to bicubic. + [#289](https://github.com/lovell/sharp/issues/289) + [@mahnunchik](https://github.com/mahnunchik) + +* Pre-extract rotatation should not swap width/height. + [#296](https://github.com/lovell/sharp/issues/296) + [@asilvas](https://github.com/asilvas) + +* Ensure 16-bit+alpha input images are (un)premultiplied correctly. + [#301](https://github.com/lovell/sharp/issues/301) + [@izaakschroeder](https://github.com/izaakschroeder) + +* Add `threshold` operation. + [#303](https://github.com/lovell/sharp/pull/303) + [@dacarley](https://github.com/dacarley) + +* Add `negate` operation. + [#306](https://github.com/lovell/sharp/pull/306) + [@dacarley](https://github.com/dacarley) + +* Support `options` Object with existing `extract` operation. + [#309](https://github.com/lovell/sharp/pull/309) + [@papandreou](https://github.com/papandreou) diff --git a/docs/src/content/docs/changelog/v0.12.1.md b/docs/src/content/docs/changelog/v0.12.1.md new file mode 100644 index 000000000..61fb46a80 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.12.1.md @@ -0,0 +1,15 @@ +--- +title: v0.12.1 - 12th December 2015 +slug: changelog/v0.12.1 +--- + +* Allow use of SIMD vector instructions (via liborc) to be toggled on/off. + [#172](https://github.com/lovell/sharp/issues/172) + [@bkw](https://github.com/bkw) + [@puzrin](https://github.com/puzrin) + +* Ensure embedded ICC profiles output with perceptual intent. + [#321](https://github.com/lovell/sharp/issues/321) + [@vlapo](https://github.com/vlapo) + +* Use the NPM-configured HTTPS proxy, if any, for binary downloads. diff --git a/docs/src/content/docs/changelog/v0.12.2.md b/docs/src/content/docs/changelog/v0.12.2.md new file mode 100644 index 000000000..91dd927de --- /dev/null +++ b/docs/src/content/docs/changelog/v0.12.2.md @@ -0,0 +1,20 @@ +--- +title: v0.12.2 - 16th January 2016 +slug: changelog/v0.12.2 +--- + +* Upgrade libvips to v8.2.0 for improved vips_shrink. + +* Add pre-compiled libvips for ARMv6+ CPUs. + +* Ensure 16-bit input images work with embed option. + [#325](https://github.com/lovell/sharp/issues/325) + [@janaz](https://github.com/janaz) + +* Allow compilation with gmake to provide FreeBSD support. + [#326](https://github.com/lovell/sharp/issues/326) + [@c0decafe](https://github.com/c0decafe) + +* Attempt to remove temporary file after installation. + [#331](https://github.com/lovell/sharp/issues/331) + [@dtoubelis](https://github.com/dtoubelis) diff --git a/docs/src/content/docs/changelog/v0.13.0.md b/docs/src/content/docs/changelog/v0.13.0.md new file mode 100644 index 000000000..f1c989a6b --- /dev/null +++ b/docs/src/content/docs/changelog/v0.13.0.md @@ -0,0 +1,47 @@ +--- +title: v0.13.0 - 15th February 2016 +slug: changelog/v0.13.0 +--- + +* Improve vector image support by allowing control of density/DPI. + Switch pre-built libs from Imagemagick to Graphicsmagick. + [#110](https://github.com/lovell/sharp/issues/110) + [@bradisbell](https://github.com/bradisbell) + +* Add support for raw, uncompressed pixel Buffer/Stream input. + [#220](https://github.com/lovell/sharp/issues/220) + [@mikemorris](https://github.com/mikemorris) + +* Switch from libvips' C to C++ bindings, requires upgrade to v8.2.2. + [#299](https://github.com/lovell/sharp/issues/299) + +* Control number of open files in libvips' cache; breaks existing `cache` behaviour. + [#315](https://github.com/lovell/sharp/issues/315) + [@impomezia](https://github.com/impomezia) + +* Ensure 16-bit input images can be normalised and embedded onto transparent backgrounds. + [#339](https://github.com/lovell/sharp/issues/339) + [#340](https://github.com/lovell/sharp/issues/340) + [@janaz](https://github.com/janaz) + +* Ensure selected format takes precedence over any unknown output filename extension. + [#344](https://github.com/lovell/sharp/issues/344) + [@ubaltaci](https://github.com/ubaltaci) + +* Add support for libvips' PBM, PGM, PPM and FITS image format loaders. + [#347](https://github.com/lovell/sharp/issues/347) + [@oaleynik](https://github.com/oaleynik) + +* Ensure default crop gravity is center/centre. + [#351](https://github.com/lovell/sharp/pull/351) + [@joelmukuthu](https://github.com/joelmukuthu) + +* Improve support for musl libc systems e.g. Alpine Linux. + [#354](https://github.com/lovell/sharp/issues/354) + [#359](https://github.com/lovell/sharp/pull/359) + [@download13](https://github.com/download13) + [@wjordan](https://github.com/wjordan) + +* Small optimisation when reducing by an integral factor to favour shrink over affine. + +* Add support for gamma correction of images with an alpha channel. diff --git a/docs/src/content/docs/changelog/v0.13.1.md b/docs/src/content/docs/changelog/v0.13.1.md new file mode 100644 index 000000000..cbaf11ec5 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.13.1.md @@ -0,0 +1,8 @@ +--- +title: v0.13.1 - 27th February 2016 +slug: changelog/v0.13.1 +--- + +* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0. + [#366](https://github.com/lovell/sharp/issues/366) + [@diegocsandrim](https://github.com/diegocsandrim) diff --git a/docs/src/content/docs/changelog/v0.14.0.md b/docs/src/content/docs/changelog/v0.14.0.md new file mode 100644 index 000000000..a23f2e7f0 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.14.0.md @@ -0,0 +1,39 @@ +--- +title: v0.14.0 - 2nd April 2016 +slug: changelog/v0.14.0 +--- + +* Add ability to extend (pad) the edges of an image. + [#128](https://github.com/lovell/sharp/issues/128) + [@blowsie](https://github.com/blowsie) + +* Add support for Zoomify and Google tile layouts. Breaks existing tile API. + [#223](https://github.com/lovell/sharp/issues/223) + [@bdunnette](https://github.com/bdunnette) + +* Improvements to overlayWith: differing sizes/formats, gravity, buffer input. + [#239](https://github.com/lovell/sharp/issues/239) + [@chrisriley](https://github.com/chrisriley) + +* Add entropy-based crop strategy to remove least interesting edges. + [#295](https://github.com/lovell/sharp/issues/295) + [@rightaway](https://github.com/rightaway) + +* Expose density metadata; set density of images from vector input. + [#338](https://github.com/lovell/sharp/issues/338) + [@lookfirst](https://github.com/lookfirst) + +* Emit post-processing 'info' event for Stream output. + [#367](https://github.com/lovell/sharp/issues/367) + [@salzhrani](https://github.com/salzhrani) + +* Ensure output image EXIF Orientation values are within 1-8 range. + [#385](https://github.com/lovell/sharp/pull/385) + [@jtobinisaniceguy](https://github.com/jtobinisaniceguy) + +* Ensure ratios are not swapped when rotating 90/270 and ignoring aspect. + [#387](https://github.com/lovell/sharp/issues/387) + [@kleisauke](https://github.com/kleisauke) + +* Remove deprecated style of calling extract API. Breaks calls using positional arguments. + [#276](https://github.com/lovell/sharp/issues/276) diff --git a/docs/src/content/docs/changelog/v0.14.1.md b/docs/src/content/docs/changelog/v0.14.1.md new file mode 100644 index 000000000..85e88c8b1 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.14.1.md @@ -0,0 +1,26 @@ +--- +title: v0.14.1 - 16th April 2016 +slug: changelog/v0.14.1 +--- + +* Allow removal of limitation on input pixel count via limitInputPixels. Use with care. + [#250](https://github.com/lovell/sharp/issues/250) + [#316](https://github.com/lovell/sharp/pull/316) + [@anandthakker](https://github.com/anandthakker) + [@kentongray](https://github.com/kentongray) + +* Use final output image for metadata passed to callback. + [#399](https://github.com/lovell/sharp/pull/399) + [@salzhrani](https://github.com/salzhrani) + +* Add support for writing tiled images to a zip container. + [#402](https://github.com/lovell/sharp/pull/402) + [@felixbuenemann](https://github.com/felixbuenemann) + +* Allow use of embed with 1 and 2 channel images. + [#411](https://github.com/lovell/sharp/issues/411) + [@janaz](https://github.com/janaz) + +* Improve Electron compatibility by allowing node-gyp rebuilds without npm. + [#412](https://github.com/lovell/sharp/issues/412) + [@nouh](https://github.com/nouh) diff --git a/docs/src/content/docs/changelog/v0.15.0.md b/docs/src/content/docs/changelog/v0.15.0.md new file mode 100644 index 000000000..ed7807f98 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.15.0.md @@ -0,0 +1,20 @@ +--- +title: v0.15.0 - 21st May 2016 +slug: changelog/v0.15.0 +--- + +* Use libvips' new Lanczos 3 kernel as default for image reduction. + Deprecate interpolateWith method, now provided as a resize option. + [#310](https://github.com/lovell/sharp/issues/310) + [@jcupitt](https://github.com/jcupitt) + +* Take advantage of libvips v8.3 features. + Add support for libvips' new GIF and SVG loaders. + Pre-built binaries now include giflib and librsvg, exclude *magick. + Use shrink-on-load for WebP input. + Break existing sharpen API to accept sigma and improve precision. + [#369](https://github.com/lovell/sharp/issues/369) + +* Remove unnecessary (un)premultiply operations when not resizing/compositing. + [#413](https://github.com/lovell/sharp/issues/413) + [@jardakotesovec](https://github.com/jardakotesovec) diff --git a/docs/src/content/docs/changelog/v0.15.1.md b/docs/src/content/docs/changelog/v0.15.1.md new file mode 100644 index 000000000..4cfdfba7d --- /dev/null +++ b/docs/src/content/docs/changelog/v0.15.1.md @@ -0,0 +1,68 @@ +--- +title: v0.15.1 - 12th July 2016 +slug: changelog/v0.15.1 +--- + +* Concat Stream-based input in single operation for ~+3% perf and less GC. + [#429](https://github.com/lovell/sharp/issues/429) + [@papandreou](https://github.com/papandreou) + +* Add alpha channel, if required, before extend operation. + [#439](https://github.com/lovell/sharp/pull/439) + [@frulo](https://github.com/frulo) + +* Allow overlay image to be repeated across entire image via tile option. + [#443](https://github.com/lovell/sharp/pull/443) + [@lemnisk8](https://github.com/lemnisk8) + +* Add cutout option to overlayWith feature, applies only the alpha channel of the overlay image. + [#448](https://github.com/lovell/sharp/pull/448) + [@kleisauke](https://github.com/kleisauke) + +* Ensure scaling factors are calculated independently to prevent rounding errors. + [#452](https://github.com/lovell/sharp/issues/452) + [@puzrin](https://github.com/puzrin) + +* Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI. + [#456](https://github.com/lovell/sharp/pull/456) + [@kapouer](https://github.com/kapouer) + +* Add top/left offset support to overlayWith operation. + [#473](https://github.com/lovell/sharp/pull/473) + [@rnanwani](https://github.com/rnanwani) + +* Add convolve operation for kernel-based convolution. + [#479](https://github.com/lovell/sharp/pull/479) + [@mhirsch](https://github.com/mhirsch) + +* Add greyscale option to threshold operation for colourspace conversion control. + [#480](https://github.com/lovell/sharp/pull/480) + [@mhirsch](https://github.com/mhirsch) + +* Ensure ICC profiles are licenced for distribution. + [#486](https://github.com/lovell/sharp/issues/486) + [@kapouer](https://github.com/kapouer) + +* Allow images with an alpha channel to work with LAB-colourspace based sharpen. + [#490](https://github.com/lovell/sharp/issues/490) + [@jwagner](https://github.com/jwagner) + +* Add trim operation to remove "boring" edges. + [#492](https://github.com/lovell/sharp/pull/492) + [@kleisauke](https://github.com/kleisauke) + +* Add bandbool feature for channel-wise boolean operations. + [#496](https://github.com/lovell/sharp/pull/496) + [@mhirsch](https://github.com/mhirsch) + +* Add extractChannel operation to extract a channel from an image. + [#497](https://github.com/lovell/sharp/pull/497) + [@mhirsch](https://github.com/mhirsch) + +* Add ability to read and write native libvips .v files. + [#500](https://github.com/lovell/sharp/pull/500) + [@mhirsch](https://github.com/mhirsch) + +* Add boolean feature for bitwise image operations. + [#501](https://github.com/lovell/sharp/pull/501) + [@mhirsch](https://github.com/mhirsch) diff --git a/docs/src/content/docs/changelog/v0.16.0.md b/docs/src/content/docs/changelog/v0.16.0.md new file mode 100644 index 000000000..b6af330fd --- /dev/null +++ b/docs/src/content/docs/changelog/v0.16.0.md @@ -0,0 +1,42 @@ +--- +title: v0.16.0 - 18th August 2016 +slug: changelog/v0.16.0 +--- + +* Add pre-compiled libvips for OS X, ARMv7 and ARMv8. + [#312](https://github.com/lovell/sharp/issues/312) + +* Ensure boolean, bandbool, extractChannel ops occur before sRGB conversion. + [#504](https://github.com/lovell/sharp/pull/504) + [@mhirsch](https://github.com/mhirsch) + +* Recalculate factors after WebP shrink-on-load to avoid round-to-zero errors. + [#508](https://github.com/lovell/sharp/issues/508) + [@asilvas](https://github.com/asilvas) + +* Prevent boolean errors during extract operation. + [#511](https://github.com/lovell/sharp/pull/511) + [@mhirsch](https://github.com/mhirsch) + +* Add joinChannel and toColourspace/toColorspace operations. + [#513](https://github.com/lovell/sharp/pull/513) + [@mhirsch](https://github.com/mhirsch) + +* Add support for raw pixel data with boolean and withOverlay operations. + [#516](https://github.com/lovell/sharp/pull/516) + [@mhirsch](https://github.com/mhirsch) + +* Prevent bandbool creating a single channel sRGB image. + [#519](https://github.com/lovell/sharp/pull/519) + [@mhirsch](https://github.com/mhirsch) + +* Ensure ICC profiles are removed from PNG output unless withMetadata used. + [#521](https://github.com/lovell/sharp/issues/521) + [@ChrisPinewood](https://github.com/ChrisPinewood) + +* Add alpha channels, if missing, to overlayWith images. + [#540](https://github.com/lovell/sharp/pull/540) + [@cmtt](https://github.com/cmtt) + +* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... }) + [#310](https://github.com/lovell/sharp/issues/310) diff --git a/docs/src/content/docs/changelog/v0.16.1.md b/docs/src/content/docs/changelog/v0.16.1.md new file mode 100644 index 000000000..c49d28981 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.16.1.md @@ -0,0 +1,21 @@ +--- +title: v0.16.1 - 13th October 2016 +slug: changelog/v0.16.1 +--- + +* C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag. + +* Add experimental 'attention' crop strategy. + [#295](https://github.com/lovell/sharp/issues/295) + +* Include .node extension for Meteor's require() implementation. + [#537](https://github.com/lovell/sharp/issues/537) + [@isjackwild](https://github.com/isjackwild) + +* Ensure convolution kernel scale is clamped to a minimum value of 1. + [#561](https://github.com/lovell/sharp/issues/561) + [@abagshaw](https://github.com/abagshaw) + +* Correct calculation of y-axis placement when overlaying image at a fixed point. + [#566](https://github.com/lovell/sharp/issues/566) + [@Nateowami](https://github.com/Nateowami) diff --git a/docs/src/content/docs/changelog/v0.16.2.md b/docs/src/content/docs/changelog/v0.16.2.md new file mode 100644 index 000000000..8c3976c64 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.16.2.md @@ -0,0 +1,8 @@ +--- +title: v0.16.2 - 22nd October 2016 +slug: changelog/v0.16.2 +--- + +* Restrict readelf usage to Linux only when detecting global libvips version. + [#602](https://github.com/lovell/sharp/issues/602) + [@caoko](https://github.com/caoko) diff --git a/docs/src/content/docs/changelog/v0.17.0.md b/docs/src/content/docs/changelog/v0.17.0.md new file mode 100644 index 000000000..4ec1f1856 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.17.0.md @@ -0,0 +1,39 @@ +--- +title: v0.17.0 - 11th December 2016 +slug: changelog/v0.17.0 +--- + +* Drop support for versions of Node prior to v4. + +* Deprecate the following output format "option" functions: + quality, progressive, compressionLevel, withoutAdaptiveFiltering, + withoutChromaSubsampling, trellisQuantisation, trellisQuantization, + overshootDeringing, optimiseScans and optimizeScans. + Access to these is now via output format functions, for example `quality(n)` + is now `jpeg({quality: n})` and/or `webp({quality: n})`. + +* Autoconvert GIF and SVG input to PNG output if no other format is specified. + +* Expose libvips' "centre" resize option to mimic \*magick's +0.5px convention. + [#568](https://github.com/lovell/sharp/issues/568) + +* Ensure support for embedded base64 PNG and JPEG images within an SVG. + [#601](https://github.com/lovell/sharp/issues/601) + [@dynamite-ready](https://github.com/dynamite-ready) + +* Ensure premultiply operation occurs before box filter shrink. + [#605](https://github.com/lovell/sharp/issues/605) + [@CmdrShepardsPie](https://github.com/CmdrShepardsPie) + [@teroparvinen](https://github.com/teroparvinen) + +* Add support for PNG and WebP tile-based output formats (in addition to JPEG). + [#622](https://github.com/lovell/sharp/pull/622) + [@ppaskaris](https://github.com/ppaskaris) + +* Allow use of extend with greyscale input. + [#623](https://github.com/lovell/sharp/pull/623) + [@ppaskaris](https://github.com/ppaskaris) + +* Allow non-RGB input to embed/extend onto background with an alpha channel. + [#646](https://github.com/lovell/sharp/issues/646) + [@DaGaMs](https://github.com/DaGaMs) diff --git a/docs/src/content/docs/changelog/v0.17.1.md b/docs/src/content/docs/changelog/v0.17.1.md new file mode 100644 index 000000000..28cf12167 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.17.1.md @@ -0,0 +1,15 @@ +--- +title: v0.17.1 - 15th January 2017 +slug: changelog/v0.17.1 +--- + +* Improve error messages for invalid parameters. + [@spikeon](https://github.com/spikeon) + [#644](https://github.com/lovell/sharp/pull/644) + +* Simplify expression for finding vips-cpp libdir. + [#656](https://github.com/lovell/sharp/pull/656) + +* Allow HTTPS-over-HTTP proxy when downloading pre-compiled dependencies. + [@wangzhiwei1888](https://github.com/wangzhiwei1888) + [#679](https://github.com/lovell/sharp/issues/679) diff --git a/docs/src/content/docs/changelog/v0.17.2.md b/docs/src/content/docs/changelog/v0.17.2.md new file mode 100644 index 000000000..02859307e --- /dev/null +++ b/docs/src/content/docs/changelog/v0.17.2.md @@ -0,0 +1,12 @@ +--- +title: v0.17.2 - 11th February 2017 +slug: changelog/v0.17.2 +--- + +* Ensure Readable side of Stream can start flowing after Writable side has finished. + [#671](https://github.com/lovell/sharp/issues/671) + [@danhaller](https://github.com/danhaller) + +* Expose WebP alpha quality, lossless and near-lossless output options. + [#685](https://github.com/lovell/sharp/pull/685) + [@rnanwani](https://github.com/rnanwani) diff --git a/docs/src/content/docs/changelog/v0.17.3.md b/docs/src/content/docs/changelog/v0.17.3.md new file mode 100644 index 000000000..0bc48d942 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.17.3.md @@ -0,0 +1,20 @@ +--- +title: v0.17.3 - 1st April 2017 +slug: changelog/v0.17.3 +--- + +* Allow toBuffer to optionally resolve a Promise with both info and data. + [#143](https://github.com/lovell/sharp/issues/143) + [@salzhrani](https://github.com/salzhrani) + +* Create blank image of given width, height, channels and background. + [#470](https://github.com/lovell/sharp/issues/470) + [@pjarts](https://github.com/pjarts) + +* Add support for the "nearest" kernel for image reductions. + [#732](https://github.com/lovell/sharp/pull/732) + [@alice0meta](https://github.com/alice0meta) + +* Add support for TIFF compression and predictor options. + [#738](https://github.com/lovell/sharp/pull/738) + [@kristojorg](https://github.com/kristojorg) diff --git a/docs/src/content/docs/changelog/v0.18.0.md b/docs/src/content/docs/changelog/v0.18.0.md new file mode 100644 index 000000000..d8afbdd04 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.18.0.md @@ -0,0 +1,60 @@ +--- +title: v0.18.0 - 30th May 2017 +slug: changelog/v0.18.0 +--- + +* Remove the previously-deprecated output format "option" functions: + quality, progressive, compressionLevel, withoutAdaptiveFiltering, + withoutChromaSubsampling, trellisQuantisation, trellisQuantization, + overshootDeringing, optimiseScans and optimizeScans. + +* Ensure maximum output dimensions are based on the format to be used. + [#176](https://github.com/lovell/sharp/issues/176) + [@stephanebachelier](https://github.com/stephanebachelier) + +* Avoid costly (un)premultiply when using overlayWith without alpha channel. + [#573](https://github.com/lovell/sharp/issues/573) + [@strarsis](https://github.com/strarsis) + +* Include pixel depth (e.g. "uchar") when reading metadata. + [#577](https://github.com/lovell/sharp/issues/577) + [@moedusa](https://github.com/moedusa) + +* Add support for Buffer and Stream-based TIFF output. + [#587](https://github.com/lovell/sharp/issues/587) + [@strarsis](https://github.com/strarsis) + +* Expose warnings from libvips via NODE_DEBUG=sharp environment variable. + [#607](https://github.com/lovell/sharp/issues/607) + [@puzrin](https://github.com/puzrin) + +* Switch to the libvips implementation of "attention" and "entropy" crop strategies. + [#727](https://github.com/lovell/sharp/issues/727) + +* Improve performance and accuracy of nearest neighbour integral upsampling. + [#752](https://github.com/lovell/sharp/issues/752) + [@MrIbby](https://github.com/MrIbby) + +* Constructor single argument API: allow plain object, reject null/undefined. + [#768](https://github.com/lovell/sharp/issues/768) + [@kub1x](https://github.com/kub1x) + +* Ensure ARM64 pre-built binaries use correct C++11 ABI version. + [#772](https://github.com/lovell/sharp/issues/772) + [@ajiratech2](https://github.com/ajiratech2) + +* Prevent aliasing by using dynamic values for shrink(-on-load). + [#781](https://github.com/lovell/sharp/issues/781) + [@kleisauke](https://github.com/kleisauke) + +* Expose libvips' "squash" parameter to enable 1-bit TIFF output. + [#783](https://github.com/lovell/sharp/pull/783) + [@YvesBos](https://github.com/YvesBos) + +* Add support for rotation using any multiple of +/-90 degrees. + [#791](https://github.com/lovell/sharp/pull/791) + [@ncoden](https://github.com/ncoden) + +* Add "jpg" alias to toFormat as shortened form of "jpeg". + [#814](https://github.com/lovell/sharp/pull/814) + [@jingsam](https://github.com/jingsam) diff --git a/docs/src/content/docs/changelog/v0.18.1.md b/docs/src/content/docs/changelog/v0.18.1.md new file mode 100644 index 000000000..3121a9cf3 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.18.1.md @@ -0,0 +1,8 @@ +--- +title: v0.18.1 - 30th May 2017 +slug: changelog/v0.18.1 +--- + +* Remove regression from #781 that could cause incorrect shrink calculation. + [#831](https://github.com/lovell/sharp/issues/831) + [@suprMax](https://github.com/suprMax) diff --git a/docs/src/content/docs/changelog/v0.18.2.md b/docs/src/content/docs/changelog/v0.18.2.md new file mode 100644 index 000000000..2eabf8374 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.18.2.md @@ -0,0 +1,19 @@ +--- +title: v0.18.2 - 1st July 2017 +slug: changelog/v0.18.2 +--- + +* Expose libvips' xres and yres properties for TIFF output. + [#828](https://github.com/lovell/sharp/pull/828) + [@YvesBos](https://github.com/YvesBos) + +* Ensure flip and flop operations work with auto-rotate. + [#837](https://github.com/lovell/sharp/issues/837) + [@rexxars](https://github.com/rexxars) + +* Allow binary download URL override via SHARP_DIST_BASE_URL env variable. + [#841](https://github.com/lovell/sharp/issues/841) + +* Add support for Solus Linux. + [#857](https://github.com/lovell/sharp/pull/857) + [@ekremkaraca](https://github.com/ekremkaraca) diff --git a/docs/src/content/docs/changelog/v0.18.3.md b/docs/src/content/docs/changelog/v0.18.3.md new file mode 100644 index 000000000..ea66e8112 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.18.3.md @@ -0,0 +1,12 @@ +--- +title: v0.18.3 - 13th September 2017 +slug: changelog/v0.18.3 +--- + +* Skip shrink-on-load when trimming. + [#888](https://github.com/lovell/sharp/pull/888) + [@kleisauke](https://github.com/kleisauke) + +* Migrate from got to simple-get for basic auth support. + [#945](https://github.com/lovell/sharp/pull/945) + [@pbomb](https://github.com/pbomb) diff --git a/docs/src/content/docs/changelog/v0.18.4.md b/docs/src/content/docs/changelog/v0.18.4.md new file mode 100644 index 000000000..002e06c26 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.18.4.md @@ -0,0 +1,8 @@ +--- +title: v0.18.4 - 18th September 2017 +slug: changelog/v0.18.4 +--- + +* Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC. + [#950](https://github.com/lovell/sharp/issues/950) + [@lfdoherty](https://github.com/lfdoherty) diff --git a/docs/src/content/docs/changelog/v0.19.0.md b/docs/src/content/docs/changelog/v0.19.0.md new file mode 100644 index 000000000..d9ecfefcb --- /dev/null +++ b/docs/src/content/docs/changelog/v0.19.0.md @@ -0,0 +1,46 @@ +--- +title: v0.19.0 - 11th January 2018 +slug: changelog/v0.19.0 +--- + +* Expose offset coordinates of strategy-based crop. + [#868](https://github.com/lovell/sharp/issues/868) + [@mirohristov-com](https://github.com/mirohristov-com) + +* PNG output now defaults to adaptiveFiltering=false, compressionLevel=9 + [#872](https://github.com/lovell/sharp/issues/872) + [@wmertens](https://github.com/wmertens) + +* Add stats feature for pixel-derived image statistics. + [#915](https://github.com/lovell/sharp/pull/915) + [@rnanwani](https://github.com/rnanwani) + +* Add failOnError option to fail-fast on bad input image data. + [#976](https://github.com/lovell/sharp/pull/976) + [@mceachen](https://github.com/mceachen) + +* Resize: switch to libvips' implementation, make fastShrinkOnLoad optional, remove interpolator and centreSampling options. + [#977](https://github.com/lovell/sharp/pull/977) + [@jardakotesovec](https://github.com/jardakotesovec) + +* Attach finish event listener to a clone only for Stream-based input. + [#995](https://github.com/lovell/sharp/issues/995) + [@whmountains](https://github.com/whmountains) + +* Add tilecache before smartcrop to avoid over-computation of previous operations. + [#1028](https://github.com/lovell/sharp/issues/1028) + [@coffeebite](https://github.com/coffeebite) + +* Prevent toFile extension taking precedence over requested format. + [#1037](https://github.com/lovell/sharp/issues/1037) + [@tomgallagher](https://github.com/tomgallagher) + +* Add support for gravity option to existing embed feature. + [#1038](https://github.com/lovell/sharp/pull/1038) + [@AzureByte](https://github.com/AzureByte) + +* Expose IPTC and XMP metadata when available. + [#1079](https://github.com/lovell/sharp/pull/1079) + [@oaleynik](https://github.com/oaleynik) + +* TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour. diff --git a/docs/src/content/docs/changelog/v0.19.1.md b/docs/src/content/docs/changelog/v0.19.1.md new file mode 100644 index 000000000..deeea20d1 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.19.1.md @@ -0,0 +1,16 @@ +--- +title: v0.19.1 - 24th February 2018 +slug: changelog/v0.19.1 +--- + +* Expose libvips' linear transform feature. + [#1024](https://github.com/lovell/sharp/pull/1024) + [@3epnm](https://github.com/3epnm) + +* Expose angle option for tile-based output. + [#1121](https://github.com/lovell/sharp/pull/1121) + [@BiancoA](https://github.com/BiancoA) + +* Prevent crop operation when image already at or below target dimensions. + [#1134](https://github.com/lovell/sharp/issues/1134) + [@pieh](https://github.com/pieh) diff --git a/docs/src/content/docs/changelog/v0.20.0.md b/docs/src/content/docs/changelog/v0.20.0.md new file mode 100644 index 000000000..1f0bf56a2 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.0.md @@ -0,0 +1,7 @@ +--- +title: v0.20.0 - 5th March 2018 +slug: changelog/v0.20.0 +--- + +* Add support for prebuilt sharp binaries on common platforms. + [#186](https://github.com/lovell/sharp/issues/186) diff --git a/docs/src/content/docs/changelog/v0.20.1.md b/docs/src/content/docs/changelog/v0.20.1.md new file mode 100644 index 000000000..f4af4314c --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.1.md @@ -0,0 +1,15 @@ +--- +title: v0.20.1 - 17th March 2018 +slug: changelog/v0.20.1 +--- + +* Improve installation experience when a globally-installed libvips below the minimum required version is found. + [#1148](https://github.com/lovell/sharp/issues/1148) + +* Prevent smartcrop error when cumulative rounding is below target size. + [#1154](https://github.com/lovell/sharp/issues/1154) + [@ralrom](https://github.com/ralrom) + +* Expose libvips' median filter operation. + [#1161](https://github.com/lovell/sharp/pull/1161) + [@BiancoA](https://github.com/BiancoA) diff --git a/docs/src/content/docs/changelog/v0.20.2.md b/docs/src/content/docs/changelog/v0.20.2.md new file mode 100644 index 000000000..202d5c339 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.2.md @@ -0,0 +1,20 @@ +--- +title: v0.20.2 - 28th April 2018 +slug: changelog/v0.20.2 +--- + +* Add tint operation to set image chroma. + [#825](https://github.com/lovell/sharp/pull/825) + [@rikh42](https://github.com/rikh42) + +* Add environment variable to ignore globally-installed libvips. + [#1165](https://github.com/lovell/sharp/pull/1165) + [@oncletom](https://github.com/oncletom) + +* Add support for page selection with multi-page input (GIF/TIFF). + [#1204](https://github.com/lovell/sharp/pull/1204) + [@woolite64](https://github.com/woolite64) + +* Add support for Group4 (CCITTFAX4) compression with TIFF output. + [#1208](https://github.com/lovell/sharp/pull/1208) + [@woolite64](https://github.com/woolite64) diff --git a/docs/src/content/docs/changelog/v0.20.3.md b/docs/src/content/docs/changelog/v0.20.3.md new file mode 100644 index 000000000..b00c64910 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.3.md @@ -0,0 +1,8 @@ +--- +title: v0.20.3 - 29th May 2018 +slug: changelog/v0.20.3 +--- + +* Fix tint operation by ensuring LAB interpretation and allowing negative values. + [#1235](https://github.com/lovell/sharp/issues/1235) + [@wezside](https://github.com/wezside) diff --git a/docs/src/content/docs/changelog/v0.20.4.md b/docs/src/content/docs/changelog/v0.20.4.md new file mode 100644 index 000000000..a9580dea3 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.4.md @@ -0,0 +1,12 @@ +--- +title: v0.20.4 - 20th June 2018 +slug: changelog/v0.20.4 +--- + +* Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation. + [#1241](https://github.com/lovell/sharp/issues/1241) + [@anahit42](https://github.com/anahit42) + +* Ensure extractChannel sets correct single-channel colour space interpretation. + [#1257](https://github.com/lovell/sharp/issues/1257) + [@jeremychone](https://github.com/jeremychone) diff --git a/docs/src/content/docs/changelog/v0.20.5.md b/docs/src/content/docs/changelog/v0.20.5.md new file mode 100644 index 000000000..07f166149 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.5.md @@ -0,0 +1,8 @@ +--- +title: v0.20.5 - 27th June 2018 +slug: changelog/v0.20.5 +--- + +* Expose libjpeg optimize_coding flag. + [#1265](https://github.com/lovell/sharp/pull/1265) + [@tomlokhorst](https://github.com/tomlokhorst) diff --git a/docs/src/content/docs/changelog/v0.20.6.md b/docs/src/content/docs/changelog/v0.20.6.md new file mode 100644 index 000000000..284a67a27 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.6.md @@ -0,0 +1,33 @@ +--- +title: v0.20.6 - 20th August 2018 +slug: changelog/v0.20.6 +--- + +* Add removeAlpha operation to remove alpha channel, if any. + [#1248](https://github.com/lovell/sharp/issues/1248) + +* Expose mozjpeg quant_table flag. + [#1285](https://github.com/lovell/sharp/pull/1285) + [@rexxars](https://github.com/rexxars) + +* Allow full WebP alphaQuality range of 0-100. + [#1290](https://github.com/lovell/sharp/pull/1290) + [@sylvaindumont](https://github.com/sylvaindumont) + +* Cache libvips binaries to reduce re-install time. + [#1301](https://github.com/lovell/sharp/issues/1301) + +* Ensure vendor platform mismatch throws error at install time. + [#1303](https://github.com/lovell/sharp/issues/1303) + +* Improve install time error messages for FreeBSD users. + [#1310](https://github.com/lovell/sharp/issues/1310) + +* Ensure extractChannel works with 16-bit images. + [#1330](https://github.com/lovell/sharp/issues/1330) + +* Expose depth option for tile-based output. + [#1342](https://github.com/lovell/sharp/pull/1342) + [@alundavies](https://github.com/alundavies) + +* Add experimental entropy field to stats response. diff --git a/docs/src/content/docs/changelog/v0.20.7.md b/docs/src/content/docs/changelog/v0.20.7.md new file mode 100644 index 000000000..e9503a29d --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.7.md @@ -0,0 +1,7 @@ +--- +title: v0.20.7 - 21st August 2018 +slug: changelog/v0.20.7 +--- + +* Use copy+unlink if rename operation fails during installation. + [#1345](https://github.com/lovell/sharp/issues/1345) diff --git a/docs/src/content/docs/changelog/v0.20.8.md b/docs/src/content/docs/changelog/v0.20.8.md new file mode 100644 index 000000000..8ab9a062a --- /dev/null +++ b/docs/src/content/docs/changelog/v0.20.8.md @@ -0,0 +1,12 @@ +--- +title: v0.20.8 - 5th September 2018 +slug: changelog/v0.20.8 +--- + +* Avoid race conditions when creating directories during installation. + [#1358](https://github.com/lovell/sharp/pull/1358) + [@ajhool](https://github.com/ajhool) + +* Accept floating point values for input density parameter. + [#1362](https://github.com/lovell/sharp/pull/1362) + [@aeirola](https://github.com/aeirola) diff --git a/docs/src/content/docs/changelog/v0.21.0.md b/docs/src/content/docs/changelog/v0.21.0.md new file mode 100644 index 000000000..762cc65a4 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.21.0.md @@ -0,0 +1,39 @@ +--- +title: v0.21.0 - 4th October 2018 +slug: changelog/v0.21.0 +--- + +* Deprecate the following resize-related functions: + `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. + Access to these is now via options passed to the `resize` function. + For example: + `embed('north')` is now `resize(width, height, { fit: 'contain', position: 'north' })`, + `crop('attention')` is now `resize(width, height, { fit: 'cover', position: 'attention' })`, + `max().withoutEnlargement()` is now `resize(width, height, { fit: 'inside', withoutEnlargement: true })`. + [#1135](https://github.com/lovell/sharp/issues/1135) + +* Deprecate the `background` function. + Per-operation `background` options added to `resize`, `extend` and `flatten` operations. + [#1392](https://github.com/lovell/sharp/issues/1392) + +* Add `size` to `metadata` response (Stream and Buffer input only). + [#695](https://github.com/lovell/sharp/issues/695) + +* Switch from custom trim operation to `vips_find_trim`. + [#914](https://github.com/lovell/sharp/issues/914) + +* Add `chromaSubsampling` and `isProgressive` properties to `metadata` response. + [#1186](https://github.com/lovell/sharp/issues/1186) + +* Drop Node 4 support. + [#1212](https://github.com/lovell/sharp/issues/1212) + +* Enable SIMD convolution by default. + [#1213](https://github.com/lovell/sharp/issues/1213) + +* Add experimental prebuilt binaries for musl-based Linux. + [#1379](https://github.com/lovell/sharp/issues/1379) + +* Add support for arbitrary rotation angle via vips_rotate. + [#1385](https://github.com/lovell/sharp/pull/1385) + [@freezy](https://github.com/freezy) diff --git a/docs/src/content/docs/changelog/v0.21.1.md b/docs/src/content/docs/changelog/v0.21.1.md new file mode 100644 index 000000000..bb685ef2b --- /dev/null +++ b/docs/src/content/docs/changelog/v0.21.1.md @@ -0,0 +1,31 @@ +--- +title: v0.21.1 - 7th December 2018 +slug: changelog/v0.21.1 +--- + +* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`. + [#1422](https://github.com/lovell/sharp/pull/1422) + [@SethWen](https://github.com/SethWen) + +* Ensure `channel` metadata is correct for raw, greyscale output. + [#1425](https://github.com/lovell/sharp/issues/1425) + +* Add support for the "mitchell" kernel for image reductions. + [#1438](https://github.com/lovell/sharp/pull/1438) + [@Daiz](https://github.com/Daiz) + +* Allow separate parameters for gamma encoding and decoding. + [#1439](https://github.com/lovell/sharp/pull/1439) + [@Daiz](https://github.com/Daiz) + +* Build prototype with `Object.assign` to allow minification. + [#1475](https://github.com/lovell/sharp/pull/1475) + [@jaubourg](https://github.com/jaubourg) + +* Expose libvips' recombination matrix operation. + [#1477](https://github.com/lovell/sharp/pull/1477) + [@fromkeith](https://github.com/fromkeith) + +* Expose libvips' pyramid/tile options for TIFF output. + [#1483](https://github.com/lovell/sharp/pull/1483) + [@mbklein](https://github.com/mbklein) diff --git a/docs/src/content/docs/changelog/v0.21.2.md b/docs/src/content/docs/changelog/v0.21.2.md new file mode 100644 index 000000000..be7f6758d --- /dev/null +++ b/docs/src/content/docs/changelog/v0.21.2.md @@ -0,0 +1,27 @@ +--- +title: v0.21.2 - 13th January 2019 +slug: changelog/v0.21.2 +--- + +* Ensure all metadata is removed from PNG output unless `withMetadata` used. + +* Ensure shortest edge is at least one pixel after resizing. + [#1003](https://github.com/lovell/sharp/issues/1003) + +* Add `ensureAlpha` operation to add an alpha channel, if missing. + [#1153](https://github.com/lovell/sharp/issues/1153) + +* Expose `pages` and `pageHeight` metadata for multi-page input images. + [#1205](https://github.com/lovell/sharp/issues/1205) + +* Expose PNG output options requiring libimagequant. + [#1484](https://github.com/lovell/sharp/issues/1484) + +* Expose underlying error message for invalid input. + [#1505](https://github.com/lovell/sharp/issues/1505) + +* Prevent mutatation of options passed to `jpeg`. + [#1516](https://github.com/lovell/sharp/issues/1516) + +* Ensure forced output format applied correctly when output chaining. + [#1528](https://github.com/lovell/sharp/issues/1528) diff --git a/docs/src/content/docs/changelog/v0.21.3.md b/docs/src/content/docs/changelog/v0.21.3.md new file mode 100644 index 000000000..1cde28c63 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.21.3.md @@ -0,0 +1,9 @@ +--- +title: v0.21.3 - 19th January 2019 +slug: changelog/v0.21.3 +--- + +* Input image decoding now fails fast, set `failOnError` to change this behaviour. + +* Failed filesystem-based input now separates missing file and invalid format errors. + [#1542](https://github.com/lovell/sharp/issues/1542) diff --git a/docs/src/content/docs/changelog/v0.22.0.md b/docs/src/content/docs/changelog/v0.22.0.md new file mode 100644 index 000000000..797c743bc --- /dev/null +++ b/docs/src/content/docs/changelog/v0.22.0.md @@ -0,0 +1,20 @@ +--- +title: v0.22.0 - 18th March 2019 +slug: changelog/v0.22.0 +--- + +* Remove functions previously deprecated in v0.21.0: + `background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. + +* Add `composite` operation supporting multiple images and blend modes; deprecate `overlayWith`. + [#728](https://github.com/lovell/sharp/issues/728) + +* Add support for `pages` input option for multi-page input. + [#1566](https://github.com/lovell/sharp/issues/1566) + +* Allow Stream-based input of raw pixel data. + [#1579](https://github.com/lovell/sharp/issues/1579) + +* Add support for `page` input option to GIF and PDF. + [#1595](https://github.com/lovell/sharp/pull/1595) + [@ramiel](https://github.com/ramiel) diff --git a/docs/src/content/docs/changelog/v0.22.1.md b/docs/src/content/docs/changelog/v0.22.1.md new file mode 100644 index 000000000..cf8e3d464 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.22.1.md @@ -0,0 +1,15 @@ +--- +title: v0.22.1 - 25th April 2019 +slug: changelog/v0.22.1 +--- + +* Add `modulate` operation for brightness, saturation and hue. + [#1601](https://github.com/lovell/sharp/pull/1601) + [@Goues](https://github.com/Goues) + +* Improve help messaging should `require("sharp")` fail. + [#1638](https://github.com/lovell/sharp/pull/1638) + [@sidharthachatterjee](https://github.com/sidharthachatterjee) + +* Add support for Node 12. + [#1668](https://github.com/lovell/sharp/issues/1668) diff --git a/docs/src/content/docs/changelog/v0.23.0.md b/docs/src/content/docs/changelog/v0.23.0.md new file mode 100644 index 000000000..24b18827c --- /dev/null +++ b/docs/src/content/docs/changelog/v0.23.0.md @@ -0,0 +1,32 @@ +--- +title: v0.23.0 - 29th July 2019 +slug: changelog/v0.23.0 +--- + +* Remove `overlayWith` previously deprecated in v0.22.0. + +* Add experimental support for HEIF images. Requires libvips compiled with libheif. + [#1105](https://github.com/lovell/sharp/issues/1105) + +* Expose libwebp `smartSubsample` and `reductionEffort` options. + [#1545](https://github.com/lovell/sharp/issues/1545) + +* Add experimental support for Worker Threads. + [#1558](https://github.com/lovell/sharp/issues/1558) + +* Use libvips' built-in CMYK and sRGB profiles when required. + [#1619](https://github.com/lovell/sharp/issues/1619) + +* Drop support for Node.js versions 6 and 11. + [#1674](https://github.com/lovell/sharp/issues/1674) + +* Expose `skipBlanks` option for tile-based output. + [#1687](https://github.com/lovell/sharp/pull/1687) + [@RaboliotTheGrey](https://github.com/RaboliotTheGrey) + +* Allow use of `failOnError` option with Stream-based input. + [#1691](https://github.com/lovell/sharp/issues/1691) + +* Fix rotate/extract ordering for non-90 angles. + [#1755](https://github.com/lovell/sharp/pull/1755) + [@iovdin](https://github.com/iovdin) diff --git a/docs/src/content/docs/changelog/v0.23.1.md b/docs/src/content/docs/changelog/v0.23.1.md new file mode 100644 index 000000000..c180d9952 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.23.1.md @@ -0,0 +1,24 @@ +--- +title: v0.23.1 - 26th September 2019 +slug: changelog/v0.23.1 +--- + +* Ensure `sharp.format.vips` is present and correct (filesystem only). + [#1813](https://github.com/lovell/sharp/issues/1813) + +* Ensure invalid `width` and `height` provided as options to `resize` throw. + [#1817](https://github.com/lovell/sharp/issues/1817) + +* Allow use of 'heic' and 'heif' identifiers with `toFormat`. + [#1834](https://github.com/lovell/sharp/pull/1834) + [@jaubourg](https://github.com/jaubourg) + +* Add `premultiplied` option to `composite` operation. + [#1835](https://github.com/lovell/sharp/pull/1835) + [@Andargor](https://github.com/Andargor) + +* Allow instance reuse with differing `toBuffer` options. + [#1860](https://github.com/lovell/sharp/pull/1860) + [@RaboliotTheGrey](https://github.com/RaboliotTheGrey) + +* Ensure image is at least 3x3 pixels before attempting trim operation. diff --git a/docs/src/content/docs/changelog/v0.23.2.md b/docs/src/content/docs/changelog/v0.23.2.md new file mode 100644 index 000000000..1eb59491d --- /dev/null +++ b/docs/src/content/docs/changelog/v0.23.2.md @@ -0,0 +1,12 @@ +--- +title: v0.23.2 - 28th October 2019 +slug: changelog/v0.23.2 +--- + +* Add `background` option to tile output operation. + [#1924](https://github.com/lovell/sharp/pull/1924) + [@neave](https://github.com/neave) + +* Add support for Node.js 13. + [#1932](https://github.com/lovell/sharp/pull/1932) + [@MayhemYDG](https://github.com/MayhemYDG) diff --git a/docs/src/content/docs/changelog/v0.23.3.md b/docs/src/content/docs/changelog/v0.23.3.md new file mode 100644 index 000000000..4c19795a5 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.23.3.md @@ -0,0 +1,18 @@ +--- +title: v0.23.3 - 17th November 2019 +slug: changelog/v0.23.3 +--- + +* Ensure `trim` operation supports images contained in the alpha channel. + [#1597](https://github.com/lovell/sharp/issues/1597) + +* Ensure tile `overlap` option works as expected. + [#1921](https://github.com/lovell/sharp/pull/1921) + [@rustyguts](https://github.com/rustyguts) + +* Allow compilation on FreeBSD and variants (broken since v0.23.0) + [#1952](https://github.com/lovell/sharp/pull/1952) + [@pouya-eghbali](https://github.com/pouya-eghbali) + +* Ensure `modulate` and other colour-based operations can co-exist. + [#1958](https://github.com/lovell/sharp/issues/1958) diff --git a/docs/src/content/docs/changelog/v0.23.4.md b/docs/src/content/docs/changelog/v0.23.4.md new file mode 100644 index 000000000..3d8bfed73 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.23.4.md @@ -0,0 +1,12 @@ +--- +title: v0.23.4 - 5th December 2019 +slug: changelog/v0.23.4 +--- + +* Handle zero-length Buffer objects when using Node.js v13.2.0+. + +* Expose raw TIFFTAG_PHOTOSHOP metadata. + [#1600](https://github.com/lovell/sharp/issues/1600) + +* Improve thread safety by using copy-on-write when updating metadata. + [#1986](https://github.com/lovell/sharp/issues/1986) diff --git a/docs/src/content/docs/changelog/v0.24.0.md b/docs/src/content/docs/changelog/v0.24.0.md new file mode 100644 index 000000000..378dad771 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.24.0.md @@ -0,0 +1,28 @@ +--- +title: v0.24.0 - 16th January 2020 +slug: changelog/v0.24.0 +--- + +* Drop support for Node.js 8. + [#1910](https://github.com/lovell/sharp/issues/1910) + +* Drop support for undefined input where options also provided. + [#1768](https://github.com/lovell/sharp/issues/1768) + +* Move `limitInputPixels` and `sequentialRead` to input options, deprecating functions of the same name. + +* Expose `delay` and `loop` metadata for animated images. + [#1905](https://github.com/lovell/sharp/issues/1905) + +* Ensure correct colour output for 16-bit, 2-channel PNG input with ICC profile. + [#2013](https://github.com/lovell/sharp/issues/2013) + +* Prevent use of sequentialRead for rotate operations. + [#2016](https://github.com/lovell/sharp/issues/2016) + +* Correctly bind max width and height values when using withoutEnlargement. + [#2024](https://github.com/lovell/sharp/pull/2024) + [@BrychanOdlum](https://github.com/BrychanOdlum) + +* Add support for input with 16-bit RGB profile. + [#2037](https://github.com/lovell/sharp/issues/2037) diff --git a/docs/src/content/docs/changelog/v0.24.1.md b/docs/src/content/docs/changelog/v0.24.1.md new file mode 100644 index 000000000..2155c2005 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.24.1.md @@ -0,0 +1,10 @@ +--- +title: v0.24.1 - 15th February 2020 +slug: changelog/v0.24.1 +--- + +* Prevent use of sequentialRead for EXIF-based rotate operation. + [#2042](https://github.com/lovell/sharp/issues/2042) + +* Ensure RGBA LZW TIFF returns correct channel count. + [#2064](https://github.com/lovell/sharp/issues/2064) diff --git a/docs/src/content/docs/changelog/v0.25.0.md b/docs/src/content/docs/changelog/v0.25.0.md new file mode 100644 index 000000000..b42978a98 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.25.0.md @@ -0,0 +1,18 @@ +--- +title: v0.25.0 - 7th March 2020 +slug: changelog/v0.25.0 +--- + +* Remove `limitInputPixels` and `sequentialRead` previously deprecated in v0.24.0. + +* Migrate internals to N-API. + [#1282](https://github.com/lovell/sharp/issues/1282) + +* Add support for 32-bit Windows. + [#2088](https://github.com/lovell/sharp/issues/2088) + +* Ensure correct ordering of rotate-then-trim operations. + [#2087](https://github.com/lovell/sharp/issues/2087) + +* Ensure composite accepts `limitInputPixels` and `sequentialRead` input options. + [#2099](https://github.com/lovell/sharp/issues/2099) diff --git a/docs/src/content/docs/changelog/v0.25.1.md b/docs/src/content/docs/changelog/v0.25.1.md new file mode 100644 index 000000000..af0fe39d4 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.25.1.md @@ -0,0 +1,7 @@ +--- +title: v0.25.1 - 7th March 2020 +slug: changelog/v0.25.1 +--- + +* Ensure prebuilt binaries are fetched based on N-API version. + [#2117](https://github.com/lovell/sharp/issues/2117) diff --git a/docs/src/content/docs/changelog/v0.25.2.md b/docs/src/content/docs/changelog/v0.25.2.md new file mode 100644 index 000000000..298db561a --- /dev/null +++ b/docs/src/content/docs/changelog/v0.25.2.md @@ -0,0 +1,19 @@ +--- +title: v0.25.2 - 20th March 2020 +slug: changelog/v0.25.2 +--- + +* Provide prebuilt binaries for Linux ARM64v8. + +* Add IIIF layout support to tile-based output. + [#2098](https://github.com/lovell/sharp/pull/2098) + [@edsilv](https://github.com/edsilv) + +* Ensure input options are consistently and correctly detected. + [#2118](https://github.com/lovell/sharp/issues/2118) + +* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives. + [#2119](https://github.com/lovell/sharp/issues/2119) + +* Ensure AsyncWorker options are persisted. + [#2130](https://github.com/lovell/sharp/issues/2130) diff --git a/docs/src/content/docs/changelog/v0.25.3.md b/docs/src/content/docs/changelog/v0.25.3.md new file mode 100644 index 000000000..0a1a76038 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.25.3.md @@ -0,0 +1,14 @@ +--- +title: v0.25.3 - 17th May 2020 +slug: changelog/v0.25.3 +--- + +* Ensure libvips is initialised only once, improves worker thread safety. + [#2143](https://github.com/lovell/sharp/issues/2143) + +* Ensure npm platform flag is respected when copying DLLs. + [#2188](https://github.com/lovell/sharp/pull/2188) + [@dimadeveatii](https://github.com/dimadeveatii) + +* Allow SVG input with large inline images to be parsed. + [#2195](https://github.com/lovell/sharp/issues/2195) diff --git a/docs/src/content/docs/changelog/v0.25.4.md b/docs/src/content/docs/changelog/v0.25.4.md new file mode 100644 index 000000000..6bd4deb59 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.25.4.md @@ -0,0 +1,25 @@ +--- +title: v0.25.4 - 12th June 2020 +slug: changelog/v0.25.4 +--- + +* Allow libvips binary location override where version is appended. + [#2217](https://github.com/lovell/sharp/pull/2217) + [@malice00](https://github.com/malice00) + +* Enable PNG palette when setting quality, colours, colors or dither. + [#2226](https://github.com/lovell/sharp/pull/2226) + [@romaleev](https://github.com/romaleev) + +* Add `level` constructor option to use a specific level of a multi-level image. + Expose `levels` metadata for multi-level images. + [#2222](https://github.com/lovell/sharp/issues/2222) + +* Add support for named `alpha` channel to `extractChannel` operation. + [#2138](https://github.com/lovell/sharp/issues/2138) + +* Add experimental `sharpness` calculation to `stats()` response. + [#2251](https://github.com/lovell/sharp/issues/2251) + +* Emit `warning` event for non-critical processing problems. + [#2032](https://github.com/lovell/sharp/issues/2032) diff --git a/docs/src/content/docs/changelog/v0.26.0.md b/docs/src/content/docs/changelog/v0.26.0.md new file mode 100644 index 000000000..6fedd7e35 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.26.0.md @@ -0,0 +1,33 @@ +--- +title: v0.26.0 - 25th August 2020 +slug: changelog/v0.26.0 +--- + +* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+. + +* TIFF output `squash` is replaced by `bitdepth` to reduce to 1, 2 or 4 bit. + +* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`. + +* Add most `dominant` colour to image `stats`. + [#640](https://github.com/lovell/sharp/issues/640) + +* Add support for animated GIF (requires \*magick) and WebP output. + [#2012](https://github.com/lovell/sharp/pull/2012) + [@deftomat](https://github.com/deftomat) + +* Add support for libvips ImageMagick v7 loaders. + [#2258](https://github.com/lovell/sharp/pull/2258) + [@vouillon](https://github.com/vouillon) + +* Allow multi-page input via \*magick. + [#2259](https://github.com/lovell/sharp/pull/2259) + [@vouillon](https://github.com/vouillon) + +* Add support to `withMetadata` for custom ICC profile. + [#2271](https://github.com/lovell/sharp/pull/2271) + [@roborourke](https://github.com/roborourke) + +* Ensure prebuilt binaries for ARM default to v7 when using Electron. + [#2292](https://github.com/lovell/sharp/pull/2292) + [@diegodev3](https://github.com/diegodev3) diff --git a/docs/src/content/docs/changelog/v0.26.1.md b/docs/src/content/docs/changelog/v0.26.1.md new file mode 100644 index 000000000..080b05da3 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.26.1.md @@ -0,0 +1,22 @@ +--- +title: v0.26.1 - 20th September 2020 +slug: changelog/v0.26.1 +--- + +* Ensure correct pageHeight when verifying multi-page image dimensions. + [#2343](https://github.com/lovell/sharp/pull/2343) + [@derom](https://github.com/derom) + +* Allow input density range up to 100000 DPI. + [#2348](https://github.com/lovell/sharp/pull/2348) + [@stefanprobst](https://github.com/stefanprobst) + +* Ensure animation-related properties can be set for Stream-based input. + [#2369](https://github.com/lovell/sharp/pull/2369) + [@AcrylicShrimp](https://github.com/AcrylicShrimp) + +* Ensure `stats` can be calculated for 1x1 input. + [#2372](https://github.com/lovell/sharp/issues/2372) + +* Ensure animated GIF output is optimised. + [#2376](https://github.com/lovell/sharp/issues/2376) diff --git a/docs/src/content/docs/changelog/v0.26.2.md b/docs/src/content/docs/changelog/v0.26.2.md new file mode 100644 index 000000000..2031300a2 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.26.2.md @@ -0,0 +1,15 @@ +--- +title: v0.26.2 - 14th October 2020 +slug: changelog/v0.26.2 +--- + +* Add support for EXR input. Requires libvips compiled with OpenEXR. + [#698](https://github.com/lovell/sharp/issues/698) + +* Ensure support for yarn v2. + [#2379](https://github.com/lovell/sharp/pull/2379) + [@jalovatt](https://github.com/jalovatt) + +* Add centre/center option to tile-based output. + [#2397](https://github.com/lovell/sharp/pull/2397) + [@beig](https://github.com/beig) diff --git a/docs/src/content/docs/changelog/v0.26.3.md b/docs/src/content/docs/changelog/v0.26.3.md new file mode 100644 index 000000000..a12a54480 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.26.3.md @@ -0,0 +1,12 @@ +--- +title: v0.26.3 - 16th November 2020 +slug: changelog/v0.26.3 +--- + +* Expose libvips' affine operation. + [#2336](https://github.com/lovell/sharp/pull/2336) + [@guillevc](https://github.com/guillevc) + +* Fallback to tar.gz for prebuilt libvips when Brotli not available. + [#2412](https://github.com/lovell/sharp/pull/2412) + [@ascorbic](https://github.com/ascorbic) diff --git a/docs/src/content/docs/changelog/v0.27.0.md b/docs/src/content/docs/changelog/v0.27.0.md new file mode 100644 index 000000000..d21378479 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.27.0.md @@ -0,0 +1,15 @@ +--- +title: v0.27.0 - 22nd December 2020 +slug: changelog/v0.27.0 +--- + +* Add support for AVIF to prebuilt binaries. + +* Remove experimental status from `heif` output, defaults are now AVIF-centric. + +* Allow negative top/left offsets for composite operation. + [#2391](https://github.com/lovell/sharp/pull/2391) + [@CurosMJ](https://github.com/CurosMJ) + +* Ensure all platforms use fontconfig for font rendering. + [#2399](https://github.com/lovell/sharp/issues/2399) diff --git a/docs/src/content/docs/changelog/v0.27.1.md b/docs/src/content/docs/changelog/v0.27.1.md new file mode 100644 index 000000000..09b24da60 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.27.1.md @@ -0,0 +1,19 @@ +--- +title: v0.27.1 - 27th January 2021 +slug: changelog/v0.27.1 +--- + +* Ensure TIFF is cast when using float predictor. + [#2502](https://github.com/lovell/sharp/pull/2502) + [@randyridge](https://github.com/randyridge) + +* Add support for Uint8Array and Uint8ClampedArray input. + [#2511](https://github.com/lovell/sharp/pull/2511) + [@leon](https://github.com/leon) + +* Revert: ensure all platforms use fontconfig for font rendering. + [#2515](https://github.com/lovell/sharp/issues/2515) + +* Expose libvips gaussnoise operation to allow creation of Gaussian noise. + [#2527](https://github.com/lovell/sharp/pull/2527) + [@alza54](https://github.com/alza54) diff --git a/docs/src/content/docs/changelog/v0.27.2.md b/docs/src/content/docs/changelog/v0.27.2.md new file mode 100644 index 000000000..b8eb9c386 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.27.2.md @@ -0,0 +1,20 @@ +--- +title: v0.27.2 - 22nd February 2021 +slug: changelog/v0.27.2 +--- + +* macOS: Prevent use of globally-installed ARM64 libvips with Rosetta x64 emulation. + [#2460](https://github.com/lovell/sharp/issues/2460) + +* Linux (musl): Prevent use of prebuilt linuxmusl-x64 binaries with musl >= 1.2.0. + [#2570](https://github.com/lovell/sharp/issues/2570) + +* Improve 16-bit grey+alpha support by using libvips' `has_alpha` detection. + [#2569](https://github.com/lovell/sharp/issues/2569) + +* Allow the use of non lower case extensions with `toFormat`. + [#2581](https://github.com/lovell/sharp/pull/2581) + [@florian-busch](https://github.com/florian-busch) + +* Allow use of `recomb` operation with single channel input. + [#2584](https://github.com/lovell/sharp/issues/2584) diff --git a/docs/src/content/docs/changelog/v0.28.0.md b/docs/src/content/docs/changelog/v0.28.0.md new file mode 100644 index 000000000..e3a72f815 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.28.0.md @@ -0,0 +1,32 @@ +--- +title: v0.28.0 - 29th March 2021 +slug: changelog/v0.28.0 +--- + +* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause). + +* Prebuilt binaries limit AVIF support to the most common 8-bit depth. + +* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults. + +* Reduce the default PNG `compressionLevel` to the more commonly used 6. + +* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation. + +* Default missing edge properties of extend operation to zero. + [#2578](https://github.com/lovell/sharp/issues/2578) + +* Ensure composite does not clip top and left offsets. + [#2594](https://github.com/lovell/sharp/pull/2594) + [@SHG42](https://github.com/SHG42) + +* Improve error handling of network failure at install time. + [#2608](https://github.com/lovell/sharp/pull/2608) + [@abradley](https://github.com/abradley) + +* Ensure `@id` attribute can be set for IIIF tile-based output. + [#2612](https://github.com/lovell/sharp/issues/2612) + [@edsilv](https://github.com/edsilv) + +* Ensure composite replicates the correct number of tiles for centred gravities. + [#2626](https://github.com/lovell/sharp/issues/2626) diff --git a/docs/src/content/docs/changelog/v0.28.1.md b/docs/src/content/docs/changelog/v0.28.1.md new file mode 100644 index 000000000..38e11a38a --- /dev/null +++ b/docs/src/content/docs/changelog/v0.28.1.md @@ -0,0 +1,15 @@ +--- +title: v0.28.1 - 5th April 2021 +slug: changelog/v0.28.1 +--- + +* Ensure all installation errors are logged with a more obvious prefix. + +* Allow `withMetadata` to set and update EXIF metadata. + [#650](https://github.com/lovell/sharp/issues/650) + +* Add support for OME-TIFF Sub Image File Directories (subIFD). + [#2557](https://github.com/lovell/sharp/issues/2557) + +* Allow `ensureAlpha` to set the alpha transparency level. + [#2634](https://github.com/lovell/sharp/issues/2634) diff --git a/docs/src/content/docs/changelog/v0.28.2.md b/docs/src/content/docs/changelog/v0.28.2.md new file mode 100644 index 000000000..9f067c44f --- /dev/null +++ b/docs/src/content/docs/changelog/v0.28.2.md @@ -0,0 +1,26 @@ +--- +title: v0.28.2 - 10th May 2021 +slug: changelog/v0.28.2 +--- + +* Allow `withMetadata` to set `density`. + [#967](https://github.com/lovell/sharp/issues/967) + +* Skip shrink-on-load where one dimension <4px. + [#2653](https://github.com/lovell/sharp/issues/2653) + +* Allow escaped proxy credentials. + [#2664](https://github.com/lovell/sharp/pull/2664) + [@msalettes](https://github.com/msalettes) + +* Add `premultiplied` flag for raw pixel data input. + [#2685](https://github.com/lovell/sharp/pull/2685) + [@mnutt](https://github.com/mnutt) + +* Detect empty input and throw a helpful error. + [#2687](https://github.com/lovell/sharp/pull/2687) + [@JakobJingleheimer](https://github.com/JakobJingleheimer) + +* Add install-time flag to skip version compatibility checks. + [#2692](https://github.com/lovell/sharp/pull/2692) + [@xemle](https://github.com/xemle) diff --git a/docs/src/content/docs/changelog/v0.28.3.md b/docs/src/content/docs/changelog/v0.28.3.md new file mode 100644 index 000000000..dff933269 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.28.3.md @@ -0,0 +1,13 @@ +--- +title: v0.28.3 - 24th May 2021 +slug: changelog/v0.28.3 +--- + +* Ensure presence of libvips, vendored or global, before invoking node-gyp. + +* Skip shrink-on-load for multi-page WebP. + [#2714](https://github.com/lovell/sharp/issues/2714) + +* Add contrast limiting adaptive histogram equalization (CLAHE) operator. + [#2726](https://github.com/lovell/sharp/pull/2726) + [@baparham](https://github.com/baparham) diff --git a/docs/src/content/docs/changelog/v0.29.0.md b/docs/src/content/docs/changelog/v0.29.0.md new file mode 100644 index 000000000..087545991 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.29.0.md @@ -0,0 +1,35 @@ +--- +title: v0.29.0 - 17th August 2021 +slug: changelog/v0.29.0 +--- + +* Drop support for Node.js 10, now requires Node.js >= 12.13.0. + +* Add `background` property to PNG and GIF image metadata. + +* Add `compression` property to HEIF image metadata. + [#2504](https://github.com/lovell/sharp/issues/2504) + +* AVIF encoding now defaults to `4:4:4` chroma subsampling. + [#2562](https://github.com/lovell/sharp/issues/2562) + +* Allow multiple platform-arch binaries in same `node_modules` installation tree. + [#2575](https://github.com/lovell/sharp/issues/2575) + +* Default to single-channel `b-w` space when `extractChannel` is used. + [#2658](https://github.com/lovell/sharp/issues/2658) + +* Allow installation directory to contain spaces (regression in v0.26.0). + [#2777](https://github.com/lovell/sharp/issues/2777) + +* Add `pipelineColourspace` operator to set the processing space. + [#2704](https://github.com/lovell/sharp/pull/2704) + [@Daiz](https://github.com/Daiz) + +* Allow bit depth to be set when using raw input and output. + [#2762](https://github.com/lovell/sharp/pull/2762) + [@mart-jansink](https://github.com/mart-jansink) + +* Allow `negate` to act only on non-alpha channels. + [#2808](https://github.com/lovell/sharp/pull/2808) + [@rexxars](https://github.com/rexxars) diff --git a/docs/src/content/docs/changelog/v0.29.1.md b/docs/src/content/docs/changelog/v0.29.1.md new file mode 100644 index 000000000..58809cbf4 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.29.1.md @@ -0,0 +1,30 @@ +--- +title: v0.29.1 - 7th September 2021 +slug: changelog/v0.29.1 +--- + +* Add `lightness` option to `modulate` operation. + [#2846](https://github.com/lovell/sharp/pull/2846) + +* Ensure correct PNG bitdepth is set based on number of colours. + [#2855](https://github.com/lovell/sharp/issues/2855) + +* Ensure background is always premultiplied when compositing. + [#2858](https://github.com/lovell/sharp/issues/2858) + +* Ensure images with P3 profiles retain full gamut. + [#2862](https://github.com/lovell/sharp/issues/2862) + +* Add support for libvips compiled with OpenJPEG. + [#2868](https://github.com/lovell/sharp/pull/2868) + +* Remove unsupported animation properties from AVIF output. + [#2870](https://github.com/lovell/sharp/issues/2870) + +* Resolve paths before comparing input/output filenames. + [#2878](https://github.com/lovell/sharp/pull/2878) + [@rexxars](https://github.com/rexxars) + +* Allow use of speed 9 (fastest) for HEIF encoding. + [#2879](https://github.com/lovell/sharp/pull/2879) + [@rexxars](https://github.com/rexxars) diff --git a/docs/src/content/docs/changelog/v0.29.2.md b/docs/src/content/docs/changelog/v0.29.2.md new file mode 100644 index 000000000..0628963f0 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.29.2.md @@ -0,0 +1,22 @@ +--- +title: v0.29.2 - 21st October 2021 +slug: changelog/v0.29.2 +--- + +* Add `timeout` function to limit processing time. + +* Ensure `sharp.versions` is populated from vendored libvips. + +* Remove animation properties from single page images. + [#2890](https://github.com/lovell/sharp/issues/2890) + +* Allow use of 'tif' to select TIFF output. + [#2893](https://github.com/lovell/sharp/pull/2893) + [@erf](https://github.com/erf) + +* Improve error message on Windows for version conflict. + [#2918](https://github.com/lovell/sharp/pull/2918) + [@dkrnl](https://github.com/dkrnl) + +* Throw error rather than exit when invalid binaries detected. + [#2931](https://github.com/lovell/sharp/issues/2931) diff --git a/docs/src/content/docs/changelog/v0.29.3.md b/docs/src/content/docs/changelog/v0.29.3.md new file mode 100644 index 000000000..e344d790e --- /dev/null +++ b/docs/src/content/docs/changelog/v0.29.3.md @@ -0,0 +1,11 @@ +--- +title: v0.29.3 - 14th November 2021 +slug: changelog/v0.29.3 +--- + +* Ensure correct dimensions when containing image resized to 1px. + [#2951](https://github.com/lovell/sharp/issues/2951) + +* Impute TIFF `xres`/`yres` from `density` provided to `withMetadata`. + [#2952](https://github.com/lovell/sharp/pull/2952) + [@mbklein](https://github.com/mbklein) diff --git a/docs/src/content/docs/changelog/v0.30.0.md b/docs/src/content/docs/changelog/v0.30.0.md new file mode 100644 index 000000000..2605fbcaa --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.0.md @@ -0,0 +1,48 @@ +--- +title: v0.30.0 - 1st February 2022 +slug: changelog/v0.30.0 +--- + +* Add support for GIF output to prebuilt binaries. + +* Reduce minimum Linux ARM64v8 glibc requirement to 2.17. + +* Verify prebuilt binaries with a Subresource Integrity check. + +* Standardise WebP `effort` option name, deprecate `reductionEffort`. + +* Standardise HEIF `effort` option name, deprecate `speed`. + +* Add support for IIIF v3 tile-based output. + +* Expose control over CPU effort for palette-based PNG output. + [#2541](https://github.com/lovell/sharp/issues/2541) + +* Improve animated (multi-page) image resize and extract. + [#2789](https://github.com/lovell/sharp/pull/2789) + [@kleisauke](https://github.com/kleisauke) + +* Expose platform and architecture of vendored binaries as `sharp.vendor`. + [#2928](https://github.com/lovell/sharp/issues/2928) + +* Ensure 16-bit PNG output uses correct bitdepth. + [#2958](https://github.com/lovell/sharp/pull/2958) + [@gforge](https://github.com/gforge) + +* Properly emit close events for duplex streams. + [#2976](https://github.com/lovell/sharp/pull/2976) + [@driannaude](https://github.com/driannaude) + +* Expose `unlimited` option for SVG and PNG input, switches off safety features. + [#2984](https://github.com/lovell/sharp/issues/2984) + +* Add `withoutReduction` option to resize operation. + [#3006](https://github.com/lovell/sharp/pull/3006) + [@christopherbradleybanks](https://github.com/christopherbradleybanks) + +* Add `resolutionUnit` as `tiff` option and expose in metadata. + [#3023](https://github.com/lovell/sharp/pull/3023) + [@ompal-sisodiya](https://github.com/ompal-sisodiya) + +* Ensure rotate-then-extract works with EXIF mirroring. + [#3024](https://github.com/lovell/sharp/issues/3024) diff --git a/docs/src/content/docs/changelog/v0.30.1.md b/docs/src/content/docs/changelog/v0.30.1.md new file mode 100644 index 000000000..06f890da8 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.1.md @@ -0,0 +1,19 @@ +--- +title: v0.30.1 - 9th February 2022 +slug: changelog/v0.30.1 +--- + +* Allow use of `toBuffer` and `toFile` on the same instance. + [#3044](https://github.com/lovell/sharp/issues/3044) + +* Skip shrink-on-load for known libjpeg rounding errors. + [#3066](https://github.com/lovell/sharp/issues/3066) + [@kleisauke](https://github.com/kleisauke) + +* Ensure withoutReduction does not interfere with contain/crop/embed. + [#3081](https://github.com/lovell/sharp/pull/3081) + [@kleisauke](https://github.com/kleisauke) + +* Ensure affine interpolator is correctly finalised. + [#3083](https://github.com/lovell/sharp/pull/3083) + [@kleisauke](https://github.com/kleisauke) diff --git a/docs/src/content/docs/changelog/v0.30.2.md b/docs/src/content/docs/changelog/v0.30.2.md new file mode 100644 index 000000000..1b5c7e1c5 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.2.md @@ -0,0 +1,17 @@ +--- +title: v0.30.2 - 2nd March 2022 +slug: changelog/v0.30.2 +--- + +* Improve performance and accuracy when compositing multiple images. + [#2286](https://github.com/lovell/sharp/issues/2286) + +* Expand pkgconfig search path for wider BSD support. + [#3106](https://github.com/lovell/sharp/issues/3106) + +* Ensure Windows C++ runtime is linked statically (regression in 0.30.0). + [#3110](https://github.com/lovell/sharp/pull/3110) + [@kleisauke](https://github.com/kleisauke) + +* Temporarily ignore greyscale ICC profiles to workaround lcms bug. + [#3112](https://github.com/lovell/sharp/issues/3112) diff --git a/docs/src/content/docs/changelog/v0.30.3.md b/docs/src/content/docs/changelog/v0.30.3.md new file mode 100644 index 000000000..77a2584a8 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.3.md @@ -0,0 +1,13 @@ +--- +title: v0.30.3 - 14th March 2022 +slug: changelog/v0.30.3 +--- + +* Allow `sharpen` options to be provided more consistently as an Object. + [#2561](https://github.com/lovell/sharp/issues/2561) + +* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation. + [#2935](https://github.com/lovell/sharp/issues/2935) + +* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2). + [#3118](https://github.com/lovell/sharp/issues/3118) diff --git a/docs/src/content/docs/changelog/v0.30.4.md b/docs/src/content/docs/changelog/v0.30.4.md new file mode 100644 index 000000000..a4f0040bf --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.4.md @@ -0,0 +1,20 @@ +--- +title: v0.30.4 - 18th April 2022 +slug: changelog/v0.30.4 +--- + +* Increase control over sensitivity to invalid images via `failOn`, deprecate `failOnError` (equivalent to `failOn: 'warning'`). + +* Ensure `create` input image has correct bit depth and colour space. + [#3139](https://github.com/lovell/sharp/issues/3139) + +* Add support for `TypedArray` input with `byteOffset` and `length`. + [#3146](https://github.com/lovell/sharp/pull/3146) + [@codepage949](https://github.com/codepage949) + +* Improve error message when attempting to render SVG input greater than 32767x32767. + [#3167](https://github.com/lovell/sharp/issues/3167) + +* Add missing file name to 'Input file is missing' error message. + [#3178](https://github.com/lovell/sharp/pull/3178) + [@Brodan](https://github.com/Brodan) diff --git a/docs/src/content/docs/changelog/v0.30.5.md b/docs/src/content/docs/changelog/v0.30.5.md new file mode 100644 index 000000000..406357619 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.5.md @@ -0,0 +1,19 @@ +--- +title: v0.30.5 - 23rd May 2022 +slug: changelog/v0.30.5 +--- + +* Install: pass `PKG_CONFIG_PATH` via env rather than substitution. + [@dwisiswant0](https://github.com/dwisiswant0) + +* Add support for `--libc` flag to improve cross-platform installation. + [#3160](https://github.com/lovell/sharp/pull/3160) + [@joonamo](https://github.com/joonamo) + +* Allow installation of prebuilt libvips binaries from filesystem. + [#3196](https://github.com/lovell/sharp/pull/3196) + [@ankurparihar](https://github.com/ankurparihar) + +* Fix rotate-then-extract for EXIF orientation 2. + [#3218](https://github.com/lovell/sharp/pull/3218) + [@jakob0fischl](https://github.com/jakob0fischl) diff --git a/docs/src/content/docs/changelog/v0.30.6.md b/docs/src/content/docs/changelog/v0.30.6.md new file mode 100644 index 000000000..340921040 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.6.md @@ -0,0 +1,10 @@ +--- +title: v0.30.6 - 30th May 2022 +slug: changelog/v0.30.6 +--- + +* Allow values for `limitInputPixels` larger than 32-bit. + [#3238](https://github.com/lovell/sharp/issues/3238) + +* Ensure brew-installed `vips` can be detected (regression in 0.30.5). + [#3239](https://github.com/lovell/sharp/issues/3239) diff --git a/docs/src/content/docs/changelog/v0.30.7.md b/docs/src/content/docs/changelog/v0.30.7.md new file mode 100644 index 000000000..fd90d905c --- /dev/null +++ b/docs/src/content/docs/changelog/v0.30.7.md @@ -0,0 +1,15 @@ +--- +title: v0.30.7 - 22nd June 2022 +slug: changelog/v0.30.7 +--- + +* Ensure tiled composition always works with outside resizing. + [#3227](https://github.com/lovell/sharp/issues/3227) + +* Allow WebP encoding effort of 0. + [#3261](https://github.com/lovell/sharp/pull/3261) + [@AlexanderTheGrey](https://github.com/AlexanderTheGrey) + +* Prevent upsampling via libwebp. + [#3267](https://github.com/lovell/sharp/pull/3267) + [@blacha](https://github.com/blacha) diff --git a/docs/src/content/docs/changelog/v0.31.0.md b/docs/src/content/docs/changelog/v0.31.0.md new file mode 100644 index 000000000..1998f9be8 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.31.0.md @@ -0,0 +1,63 @@ +--- +title: v0.31.0 - 5th September 2022 +slug: changelog/v0.31.0 +--- + +* Drop support for Node.js 12, now requires Node.js >= 14.15.0. + +* GIF output now re-uses input palette if possible. Use `reoptimise` option to generate a new palette. + +* Add WebP `minSize` and `mixed` options for greater control over animation frames. + +* Remove previously-deprecated WebP `reductionEffort` and HEIF `speed` options. Use `effort` to control these. + +* The `flip` and `flop` operations will now occur before the `rotate` operation. + +* Improve `normalise` operation with use of histogram. + [#200](https://github.com/lovell/sharp/issues/200) + +* Use combined bounding box of alpha and non-alpha channels for `trim` operation. + [#2166](https://github.com/lovell/sharp/issues/2166) + +* Add Buffer and Stream support to tile-based output. + [#2238](https://github.com/lovell/sharp/issues/2238) + +* Add input `fileSuffix` and output `alias` to `format` information. + [#2642](https://github.com/lovell/sharp/issues/2642) + +* Re-introduce support for greyscale ICC profiles (temporarily removed in 0.30.2). + [#3114](https://github.com/lovell/sharp/issues/3114) + +* Add support for WebP and PackBits `compression` options with TIFF output. + [#3198](https://github.com/lovell/sharp/issues/3198) + +* Ensure OpenSlide and FITS input works with custom libvips. + [#3226](https://github.com/lovell/sharp/issues/3226) + +* Ensure `trim` operation is a no-op when it would reduce an image to nothing. + [#3223](https://github.com/lovell/sharp/issues/3223) + +* Expose `vips_text` to create an image containing rendered text. + [#3252](https://github.com/lovell/sharp/pull/3252) + [@brahima](https://github.com/brahima) + +* Ensure only properties owned by the `withMetadata` EXIF Object are parsed. + [#3292](https://github.com/lovell/sharp/issues/3292) + +* Expand `linear` operation to allow use of per-channel arrays. + [#3303](https://github.com/lovell/sharp/pull/3303) + [@antonmarsden](https://github.com/antonmarsden) + +* Ensure the order of `rotate`, `resize` and `extend` operations is respected where possible. + Emit warnings when previous calls in the same pipeline will be ignored. + [#3319](https://github.com/lovell/sharp/issues/3319) + +* Ensure PNG bitdepth can be set for non-palette output. + [#3322](https://github.com/lovell/sharp/issues/3322) + +* Add trim option to provide a specific background colour. + [#3332](https://github.com/lovell/sharp/pull/3332) + [@mart-jansink](https://github.com/mart-jansink) + +* Ensure resized image is unpremultiplied before composite. + [#3334](https://github.com/lovell/sharp/issues/3334) diff --git a/docs/src/content/docs/changelog/v0.31.1.md b/docs/src/content/docs/changelog/v0.31.1.md new file mode 100644 index 000000000..44467514f --- /dev/null +++ b/docs/src/content/docs/changelog/v0.31.1.md @@ -0,0 +1,22 @@ +--- +title: v0.31.1 - 29th September 2022 +slug: changelog/v0.31.1 +--- + +* Upgrade to libvips v8.13.2 for upstream bug fixes. + +* Ensure `close` event occurs after `end` event for Stream-based output. + [#3313](https://github.com/lovell/sharp/issues/3313) + +* Ensure `limitInputPixels` constructor option uses uint64. + [#3349](https://github.com/lovell/sharp/pull/3349) + [@marcosc90](https://github.com/marcosc90) + +* Ensure auto-rotation works with shrink-on-load and extract (regression in 0.31.0). + [#3352](https://github.com/lovell/sharp/issues/3352) + +* Ensure AVIF output is always 8-bit. + [#3358](https://github.com/lovell/sharp/issues/3358) + +* Ensure greyscale images can be trimmed (regression in 0.31.0). + [#3386](https://github.com/lovell/sharp/issues/3386) diff --git a/docs/src/content/docs/changelog/v0.31.2.md b/docs/src/content/docs/changelog/v0.31.2.md new file mode 100644 index 000000000..c0be292b0 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.31.2.md @@ -0,0 +1,12 @@ +--- +title: v0.31.2 - 4th November 2022 +slug: changelog/v0.31.2 +--- + +* Upgrade to libvips v8.13.3 for upstream bug fixes. + +* Ensure manual flip, rotate, resize operation ordering (regression in 0.31.1) + [#3391](https://github.com/lovell/sharp/issues/3391) + +* Ensure auto-rotation works without resize (regression in 0.31.1) + [#3422](https://github.com/lovell/sharp/issues/3422) diff --git a/docs/src/content/docs/changelog/v0.31.3.md b/docs/src/content/docs/changelog/v0.31.3.md new file mode 100644 index 000000000..84c6ac899 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.31.3.md @@ -0,0 +1,34 @@ +--- +title: v0.31.3 - 21st December 2022 +slug: changelog/v0.31.3 +--- + +* Add experimental support for JPEG-XL images. Requires libvips compiled with libjxl. + [#2731](https://github.com/lovell/sharp/issues/2731) + +* Add runtime detection of V8 memory cage, ensures compatibility with Electron 21 onwards. + [#3384](https://github.com/lovell/sharp/issues/3384) + +* Expose `interFrameMaxError` and `interPaletteMaxError` GIF optimisation properties. + [#3401](https://github.com/lovell/sharp/issues/3401) + +* Allow installation on Linux with glibc patch versions e.g. Fedora 38. + [#3423](https://github.com/lovell/sharp/issues/3423) + +* Expand range of existing `sharpen` parameters to match libvips. + [#3427](https://github.com/lovell/sharp/issues/3427) + +* Prevent possible race condition awaiting metadata of Stream-based input. + [#3451](https://github.com/lovell/sharp/issues/3451) + +* Improve `extractChannel` support for 16-bit output colourspaces. + [#3453](https://github.com/lovell/sharp/issues/3453) + +* Ignore `sequentialRead` option when calculating image statistics. + [#3462](https://github.com/lovell/sharp/issues/3462) + +* Small performance improvement for operations that introduce a non-opaque background. + [#3465](https://github.com/lovell/sharp/issues/3465) + +* Ensure integral output of `linear` operation. + [#3468](https://github.com/lovell/sharp/issues/3468) diff --git a/docs/src/content/docs/changelog/v0.32.0.md b/docs/src/content/docs/changelog/v0.32.0.md new file mode 100644 index 000000000..f86104bfa --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.0.md @@ -0,0 +1,61 @@ +--- +title: v0.32.0 - 24th March 2023 +slug: changelog/v0.32.0 +--- + +* Default to using sequential rather than random access read where possible. + +* Replace GIF output `optimise` / `optimize` option with `reuse`. + +* Add `progressive` option to GIF output for interlacing. + +* Add `wrap` option to text image creation. + +* Add `formatMagick` property to metadata of images loaded via *magick. + +* Prefer integer (un)premultiply for faster resizing of RGBA images. + +* Add `ignoreIcc` input option to ignore embedded ICC profile. + +* Allow use of GPS (IFD3) EXIF metadata. + [#2767](https://github.com/lovell/sharp/issues/2767) + +* TypeScript definitions are now maintained and published directly, deprecating the `@types/sharp` package. + [#3369](https://github.com/lovell/sharp/issues/3369) + +* Prebuilt binaries: ensure macOS 10.13+ support, as documented. + [#3438](https://github.com/lovell/sharp/issues/3438) + +* Prebuilt binaries: prevent use of glib slice allocator, improves QEMU support. + [#3448](https://github.com/lovell/sharp/issues/3448) + +* Add focus point coordinates to output when using attention based crop. + [#3470](https://github.com/lovell/sharp/pull/3470) + [@ejoebstl](https://github.com/ejoebstl) + +* Expose sharp version as `sharp.versions.sharp`. + [#3471](https://github.com/lovell/sharp/issues/3471) + +* Respect `fastShrinkOnLoad` resize option for WebP input. + [#3516](https://github.com/lovell/sharp/issues/3516) + +* Reduce sharpen `sigma` maximum from 10000 to 10. + [#3521](https://github.com/lovell/sharp/issues/3521) + +* Add support for `ArrayBuffer` input. + [#3548](https://github.com/lovell/sharp/pull/3548) + [@kapouer](https://github.com/kapouer) + +* Add support to `extend` operation for `extendWith` to allow copy/mirror/repeat. + [#3556](https://github.com/lovell/sharp/pull/3556) + [@janaz](https://github.com/janaz) + +* Ensure all async JS callbacks are wrapped to help avoid possible race condition. + [#3569](https://github.com/lovell/sharp/issues/3569) + +* Prebuilt binaries: support for tile-based output temporarily removed due to licensing issue. + [#3581](https://github.com/lovell/sharp/issues/3581) + +* Add support to `normalise` for `lower` and `upper` percentiles. + [#3583](https://github.com/lovell/sharp/pull/3583) + [@LachlanNewman](https://github.com/LachlanNewman) diff --git a/docs/src/content/docs/changelog/v0.32.1.md b/docs/src/content/docs/changelog/v0.32.1.md new file mode 100644 index 000000000..a73cfb93e --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.1.md @@ -0,0 +1,30 @@ +--- +title: v0.32.1 - 27th April 2023 +slug: changelog/v0.32.1 +--- + +* Add experimental `unflatten` operation. + [#3461](https://github.com/lovell/sharp/pull/3461) + [@antonmarsden](https://github.com/antonmarsden) + +* Ensure use of `flip` operation forces random access read (regression in 0.32.0). + [#3600](https://github.com/lovell/sharp/issues/3600) + +* Ensure `linear` operation works with 16-bit input (regression in 0.31.3). + [#3605](https://github.com/lovell/sharp/issues/3605) + +* Install: ensure proxy URLs are logged correctly. + [#3615](https://github.com/lovell/sharp/pull/3615) + [@TomWis97](https://github.com/TomWis97) + +* Ensure profile-less CMYK to CMYK roundtrip skips colourspace conversion. + [#3620](https://github.com/lovell/sharp/issues/3620) + +* Add support for `modulate` operation when using non-sRGB pipeline colourspace. + [#3620](https://github.com/lovell/sharp/issues/3620) + +* Ensure `trim` operation works with CMYK images (regression in 0.31.0). + [#3636](https://github.com/lovell/sharp/issues/3636) + +* Install: coerce libc version to semver. + [#3641](https://github.com/lovell/sharp/issues/3641) diff --git a/docs/src/content/docs/changelog/v0.32.2.md b/docs/src/content/docs/changelog/v0.32.2.md new file mode 100644 index 000000000..a14aa1410 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.2.md @@ -0,0 +1,25 @@ +--- +title: v0.32.2 - 11th July 2023 +slug: changelog/v0.32.2 +--- + +* Limit HEIF output dimensions to 16384x16384, matches libvips. + +* Ensure exceptions are not thrown when terminating. + [#3569](https://github.com/lovell/sharp/issues/3569) + +* Ensure the same access method is used for all inputs (regression in 0.32.0). + [#3669](https://github.com/lovell/sharp/issues/3669) + +* Improve detection of jp2 filename extensions. + [#3674](https://github.com/lovell/sharp/pull/3674) + [@bianjunjie1981](https://github.com/bianjunjie1981) + +* Guard use of smartcrop premultiplied option to prevent warning (regression in 0.32.1). + [#3710](https://github.com/lovell/sharp/issues/3710) + +* Prevent over-compute in affine-based rotate before resize. + [#3722](https://github.com/lovell/sharp/issues/3722) + +* Allow sequential read for EXIF-based auto-orientation. + [#3725](https://github.com/lovell/sharp/issues/3725) diff --git a/docs/src/content/docs/changelog/v0.32.3.md b/docs/src/content/docs/changelog/v0.32.3.md new file mode 100644 index 000000000..ac3151982 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.3.md @@ -0,0 +1,10 @@ +--- +title: v0.32.3 - 14th July 2023 +slug: changelog/v0.32.3 +--- + +* Expose `preset` option for WebP output. + [#3639](https://github.com/lovell/sharp/issues/3639) + +* Ensure decoding remains sequential for all operations (regression in 0.32.2). + [#3725](https://github.com/lovell/sharp/issues/3725) diff --git a/docs/src/content/docs/changelog/v0.32.4.md b/docs/src/content/docs/changelog/v0.32.4.md new file mode 100644 index 000000000..f327f8d0b --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.4.md @@ -0,0 +1,11 @@ +--- +title: v0.32.4 - 21st July 2023 +slug: changelog/v0.32.4 +--- + +* Upgrade to libvips v8.14.3 for upstream bug fixes. + +* Expose ability to (un)block low-level libvips operations by name. + +* Prebuilt binaries: restore support for tile-based output. + [#3581](https://github.com/lovell/sharp/issues/3581) diff --git a/docs/src/content/docs/changelog/v0.32.5.md b/docs/src/content/docs/changelog/v0.32.5.md new file mode 100644 index 000000000..3a38ec7ec --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.5.md @@ -0,0 +1,24 @@ +--- +title: v0.32.5 - 15th August 2023 +slug: changelog/v0.32.5 +--- + +* Upgrade to libvips v8.14.4 for upstream bug fixes. + +* TypeScript: Add missing `WebpPresetEnum` to definitions. + [#3748](https://github.com/lovell/sharp/pull/3748) + [@pilotso11](https://github.com/pilotso11) + +* Ensure compilation using musl v1.2.4. + [#3755](https://github.com/lovell/sharp/pull/3755) + [@kleisauke](https://github.com/kleisauke) + +* Ensure resize with a `fit` of `inside` respects 90/270 degree rotation. + [#3756](https://github.com/lovell/sharp/issues/3756) + +* TypeScript: Ensure `minSize` property of `WebpOptions` is boolean. + [#3758](https://github.com/lovell/sharp/pull/3758) + [@sho-xizz](https://github.com/sho-xizz) + +* Ensure `withMetadata` adds default sRGB profile. + [#3761](https://github.com/lovell/sharp/issues/3761) diff --git a/docs/src/content/docs/changelog/v0.32.6.md b/docs/src/content/docs/changelog/v0.32.6.md new file mode 100644 index 000000000..6b7c3d431 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.32.6.md @@ -0,0 +1,19 @@ +--- +title: v0.32.6 - 18th September 2023 +slug: changelog/v0.32.6 +--- + +* Upgrade to libvips v8.14.5 for upstream bug fixes. + +* Ensure composite tile images are fully decoded (regression in 0.32.0). + [#3767](https://github.com/lovell/sharp/issues/3767) + +* Ensure `withMetadata` can add ICC profiles to RGB16 output. + [#3773](https://github.com/lovell/sharp/issues/3773) + +* Ensure `withMetadata` does not reduce 16-bit images to 8-bit (regression in 0.32.5). + [#3773](https://github.com/lovell/sharp/issues/3773) + +* TypeScript: Add definitions for block and unblock. + [#3799](https://github.com/lovell/sharp/pull/3799) + [@ldrick](https://github.com/ldrick) diff --git a/docs/src/content/docs/changelog/v0.33.0.md b/docs/src/content/docs/changelog/v0.33.0.md new file mode 100644 index 000000000..1ca8435de --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.0.md @@ -0,0 +1,47 @@ +--- +title: v0.33.0 - 29th November 2023 +slug: changelog/v0.33.0 +--- + +* Drop support for Node.js 14 and 16, now requires Node.js ^18.17.0 or >= 20.3.0 + +* Prebuilt binaries distributed via npm registry and installed via package manager. + +* Building from source requires dependency on `node-addon-api`. + +* Remove `sharp.vendor`. + +* Partially deprecate `withMetadata()`, use `withExif()` and `withIccProfile()`. + +* Add experimental support for WebAssembly-based runtimes. + [@RReverser](https://github.com/RReverser) + +* Options for `trim` operation must be an Object, add new `lineArt` option. + [#2363](https://github.com/lovell/sharp/issues/2363) + +* Improve luminance of `tint` operation with weighting function. + [#3338](https://github.com/lovell/sharp/issues/3338) + [@jcupitt](https://github.com/jcupitt) + +* Ensure all `Error` objects contain a `stack` property. + [#3653](https://github.com/lovell/sharp/issues/3653) + +* Make `compression` option of `heif` mandatory to help reduce HEIF vs HEIC confusion. + [#3740](https://github.com/lovell/sharp/issues/3740) + +* Ensure correct interpretation of 16-bit raw input. + [#3808](https://github.com/lovell/sharp/issues/3808) + +* Add support for `miniswhite` when using TIFF output. + [#3812](https://github.com/lovell/sharp/pull/3812) + [@dnsbty](https://github.com/dnsbty) + +* TypeScript: add missing definition for `withMetadata` boolean. + [#3823](https://github.com/lovell/sharp/pull/3823) + [@uhthomas](https://github.com/uhthomas) + +* Add more fine-grained control over output metadata. + [#3824](https://github.com/lovell/sharp/issues/3824) + +* Ensure multi-page extract remains sequential. + [#3837](https://github.com/lovell/sharp/issues/3837) diff --git a/docs/src/content/docs/changelog/v0.33.1.md b/docs/src/content/docs/changelog/v0.33.1.md new file mode 100644 index 000000000..9a280c82f --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.1.md @@ -0,0 +1,14 @@ +--- +title: v0.33.1 - 17th December 2023 +slug: changelog/v0.33.1 +--- + +* Add support for Yarn Plug'n'Play filesystem layout. + [#3888](https://github.com/lovell/sharp/issues/3888) + +* Emit warning when attempting to use invalid ICC profiles. + [#3895](https://github.com/lovell/sharp/issues/3895) + +* Ensure `VIPS_NOVECTOR` environment variable is respected. + [#3897](https://github.com/lovell/sharp/pull/3897) + [@icetee](https://github.com/icetee) diff --git a/docs/src/content/docs/changelog/v0.33.2.md b/docs/src/content/docs/changelog/v0.33.2.md new file mode 100644 index 000000000..7d88c0adf --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.2.md @@ -0,0 +1,16 @@ +--- +title: v0.33.2 - 12th January 2024 +slug: changelog/v0.33.2 +--- + +* Upgrade to libvips v8.15.1 for upstream bug fixes. + +* TypeScript: add definition for `keepMetadata`. + [#3914](https://github.com/lovell/sharp/pull/3914) + [@abhi0498](https://github.com/abhi0498) + +* Ensure `extend` operation stays sequential when copying (regression in 0.32.0). + [#3928](https://github.com/lovell/sharp/issues/3928) + +* Improve error handling for unsupported multi-page rotation. + [#3940](https://github.com/lovell/sharp/issues/3940) diff --git a/docs/src/content/docs/changelog/v0.33.3.md b/docs/src/content/docs/changelog/v0.33.3.md new file mode 100644 index 000000000..17e6639fe --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.3.md @@ -0,0 +1,21 @@ +--- +title: v0.33.3 - 23rd March 2024 +slug: changelog/v0.33.3 +--- + +* Upgrade to libvips v8.15.2 for upstream bug fixes. + +* Ensure `keepIccProfile` retains P3 and CMYK input profiles. + [#3906](https://github.com/lovell/sharp/issues/3906) + [#4008](https://github.com/lovell/sharp/issues/4008) + +* Ensure `text.wrap` property can accept `word-char` as value. + [#4028](https://github.com/lovell/sharp/pull/4028) + [@yolopunk](https://github.com/yolopunk) + +* Ensure `clone` takes a deep copy of existing options. + [#4029](https://github.com/lovell/sharp/issues/4029) + +* Add `bitdepth` option to `heif` output (prebuilt binaries support 8-bit only). + [#4036](https://github.com/lovell/sharp/pull/4036) + [@mertalev](https://github.com/mertalev) diff --git a/docs/src/content/docs/changelog/v0.33.4.md b/docs/src/content/docs/changelog/v0.33.4.md new file mode 100644 index 000000000..9c4500f91 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.4.md @@ -0,0 +1,32 @@ +--- +title: v0.33.4 - 16th May 2024 +slug: changelog/v0.33.4 +--- + +* Remove experimental status from `pipelineColourspace`. + +* Reduce default concurrency when musl thread over-subscription detected. + +* TypeScript: add missing definitions for `OverlayOptions`. + [#4048](https://github.com/lovell/sharp/pull/4048) + [@ike-gg](https://github.com/ike-gg) + +* Install: add advanced option to force use of a globally-installed libvips. + [#4060](https://github.com/lovell/sharp/issues/4060) + +* Expose `bilinear` resizing kernel (and interpolator). + [#4061](https://github.com/lovell/sharp/issues/4061) + +* Ensure `extend` operation stays sequential for multi-page TIFF (regression in 0.32.0). + [#4069](https://github.com/lovell/sharp/issues/4069) + +* Tighten validation of constructor `text` integer properties. + [#4071](https://github.com/lovell/sharp/issues/4071) + +* Simplify internal StaySequential logic. + [#4074](https://github.com/lovell/sharp/pull/4074) + [@kleisauke](https://github.com/kleisauke) + +* Ensure negate operation occurs after profile conversion. + [#4096](https://github.com/lovell/sharp/pull/4096) + [@adriaanmeuris](https://github.com/adriaanmeuris) diff --git a/docs/src/content/docs/changelog/v0.33.5.md b/docs/src/content/docs/changelog/v0.33.5.md new file mode 100644 index 000000000..7fb99baa2 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.33.5.md @@ -0,0 +1,39 @@ +--- +title: v0.33.5 - 16th August 2024 +slug: changelog/v0.33.5 +--- + +* Upgrade to libvips v8.15.3 for upstream bug fixes. + +* Add `pageHeight` and `pages` to response of multi-page output. + [#3411](https://github.com/lovell/sharp/issues/3411) + +* Ensure option to force use of a globally-installed libvips works correctly. + [#4111](https://github.com/lovell/sharp/pull/4111) + [@project0](https://github.com/project0) + +* Minimise use of `engines` property to improve yarn v1 support. + [#4130](https://github.com/lovell/sharp/issues/4130) + +* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries. + [#4132](https://github.com/lovell/sharp/issues/4132) + +* Add support to recomb operation for 4x4 matrices. + [#4147](https://github.com/lovell/sharp/pull/4147) + [@ton11797](https://github.com/ton11797) + +* Expose PNG text chunks as `comments` metadata. + [#4157](https://github.com/lovell/sharp/pull/4157) + [@nkeynes](https://github.com/nkeynes) + +* Expose optional `precision` and `minAmplitude` parameters of `blur` operation. + [#4168](https://github.com/lovell/sharp/pull/4168) + [#4172](https://github.com/lovell/sharp/pull/4172) + [@marcosc90](https://github.com/marcosc90) + +* Ensure `keepIccProfile` avoids colour transformation where possible. + [#4186](https://github.com/lovell/sharp/issues/4186) + +* TypeScript: `chromaSubsampling` metadata is optional. + [#4191](https://github.com/lovell/sharp/pull/4191) + [@DavidVaness](https://github.com/DavidVaness) diff --git a/docs/src/content/docs/changelog/v0.34.0.md b/docs/src/content/docs/changelog/v0.34.0.md new file mode 100644 index 000000000..4fc83109e --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.0.md @@ -0,0 +1,52 @@ +--- +title: v0.34.0 - 4th April 2025 +slug: changelog/v0.34.0 +--- + +* Breaking: Support array of input images to be joined or animated. + [#1580](https://github.com/lovell/sharp/issues/1580) + +* Breaking: Ensure `removeAlpha` removes all alpha channels. + [#2266](https://github.com/lovell/sharp/issues/2266) + +* Breaking: Non-animated GIF output defaults to no-loop instead of loop-forever. + [#3394](https://github.com/lovell/sharp/issues/3394) + +* Breaking: Support `info.size` on wide-character systems via upgrade to C++17. + [#3943](https://github.com/lovell/sharp/issues/3943) + +* Breaking: Ensure `background` metadata can be parsed by `color` package. + [#4090](https://github.com/lovell/sharp/issues/4090) + +* Add `isPalette` and `bitsPerSample` to metadata, deprecate `paletteBitDepth`. + +* Expose WebP `smartDeblock` output option. + +* Prevent use of linux-x64 binaries with v1 microarchitecture. + +* Add `autoOrient` operation and constructor option. + [#4151](https://github.com/lovell/sharp/pull/4151) + [@happycollision](https://github.com/happycollision) + +* TypeScript: Ensure channel counts use the correct range. + [#4197](https://github.com/lovell/sharp/pull/4197) + [@DavidVaness](https://github.com/DavidVaness) + +* Improve support for ppc64le architecture. + [#4203](https://github.com/lovell/sharp/pull/4203) + [@sumitd2](https://github.com/sumitd2) + +* Add `pdfBackground` constructor property. + [#4207](https://github.com/lovell/sharp/pull/4207) + [@calebmer](https://github.com/calebmer) + +* Expose erode and dilate operations. + [#4243](https://github.com/lovell/sharp/pull/4243) + [@qpincon](https://github.com/qpincon) + +* Add support for RGBE images. Requires libvips compiled with radiance support. + [#4316](https://github.com/lovell/sharp/pull/4316) + [@florentzabera](https://github.com/florentzabera) + +* Allow wide-gamut HEIF output at higher bitdepths. + [#4344](https://github.com/lovell/sharp/issues/4344) diff --git a/docs/src/content/docs/changelog/v0.34.1.md b/docs/src/content/docs/changelog/v0.34.1.md new file mode 100644 index 000000000..a5ae94dcf --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.1.md @@ -0,0 +1,8 @@ +--- +title: v0.34.1 - 7th April 2025 +slug: changelog/v0.34.1 +--- + +* TypeScript: Ensure new `autoOrient` property is optional. + [#4362](https://github.com/lovell/sharp/pull/4362) + [@styfle](https://github.com/styfle) diff --git a/docs/src/content/docs/changelog/v0.34.2.md b/docs/src/content/docs/changelog/v0.34.2.md new file mode 100644 index 000000000..2292304ea --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.2.md @@ -0,0 +1,28 @@ +--- +title: v0.34.2 - 20th May 2025 +slug: changelog/v0.34.2 +--- + +* 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) diff --git a/docs/src/content/docs/changelog/v0.34.3.md b/docs/src/content/docs/changelog/v0.34.3.md new file mode 100644 index 000000000..1f3016741 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.3.md @@ -0,0 +1,33 @@ +--- +title: v0.34.3 - 10th July 2025 +slug: changelog/v0.34.3 +--- + +* 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) diff --git a/docs/src/content/docs/changelog/v0.34.4.md b/docs/src/content/docs/changelog/v0.34.4.md new file mode 100644 index 000000000..83c95ce43 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.4.md @@ -0,0 +1,28 @@ +--- +title: v0.34.4 - 17th September 2025 +slug: changelog/v0.34.4 +--- + +* Upgrade to libvips v8.17.2 for upstream bug fixes. + +* Ensure TIFF `subifd` and OpenSlide `level` input options are respected (regression in 0.34.3). + +* Ensure `autoOrient` occurs before non-90 angle rotation. + [#4425](https://github.com/lovell/sharp/issues/4425) + +* Ensure `autoOrient` removes existing metadata after shrink-on-load. + [#4431](https://github.com/lovell/sharp/issues/4431) + +* TypeScript: Ensure `KernelEnum` includes `linear`. + [#4441](https://github.com/lovell/sharp/pull/4441) + [@BayanBennett](https://github.com/BayanBennett) + +* Ensure `unlimited` flag is passed upstream when reading TIFF images. + [#4446](https://github.com/lovell/sharp/issues/4446) + +* Support Electron memory cage when reading XMP metadata (regression in 0.34.3). + [#4451](https://github.com/lovell/sharp/issues/4451) + +* Add sharp-libvips rpath for yarn v5 support. + [#4452](https://github.com/lovell/sharp/pull/4452) + [@arcanis](https://github.com/arcanis) diff --git a/docs/src/content/docs/changelog/v0.34.5.md b/docs/src/content/docs/changelog/v0.34.5.md new file mode 100644 index 000000000..d8321baa4 --- /dev/null +++ b/docs/src/content/docs/changelog/v0.34.5.md @@ -0,0 +1,21 @@ +--- +title: v0.34.5 - 6th November 2025 +slug: changelog/v0.34.5 +--- + +* Upgrade to libvips v8.17.3 for upstream bug fixes. + +* Add experimental support for prebuilt Linux RISC-V 64-bit binaries. + +* Support building from source with npm v12+, deprecate `--build-from-source` flag. + [#4458](https://github.com/lovell/sharp/issues/4458) + +* Add support for BigTIFF output. + [#4459](https://github.com/lovell/sharp/pull/4459) + [@throwbi](https://github.com/throwbi) + +* Improve error messaging when only warnings issued. + [#4465](https://github.com/lovell/sharp/issues/4465) + +* Simplify ICC processing when retaining input profiles. + [#4468](https://github.com/lovell/sharp/issues/4468) diff --git a/docs/src/content/docs/index.md b/docs/src/content/docs/index.md index 4c04edd88..3ba704f43 100644 --- a/docs/src/content/docs/index.md +++ b/docs/src/content/docs/index.md @@ -25,7 +25,7 @@ rotation, extraction, compositing and gamma correction are available. Most modern macOS, Windows and Linux systems do not require any additional install or runtime dependencies. -```sh +```sh frame="none" npm install sharp ``` diff --git a/docs/src/content/docs/install.md b/docs/src/content/docs/install.md index 0ca0290b1..083db5ccd 100644 --- a/docs/src/content/docs/install.md +++ b/docs/src/content/docs/install.md @@ -12,28 +12,29 @@ If a package manager lockfile must support multiple platforms, please see the [cross-platform](#cross-platform) section to help decide which package manager is appropriate. -```sh +```sh frame="none" npm install sharp ``` -```sh +```sh frame="none" pnpm add sharp ``` -When using `pnpm`, you may need to add `sharp` to -[ignoredBuiltDependencies](https://pnpm.io/package_json#pnpmignoredbuiltdependencies) +When using `pnpm`, add `sharp` to +[ignoredBuiltDependencies](https://pnpm.io/settings#ignoredbuiltdependencies) to silence warnings. -```sh +```sh frame="none" yarn add sharp ``` -```sh +```sh frame="none" bun add sharp ``` -```sh -deno run --allow-ffi ... +```sh frame="none" +deno add --quiet npm:sharp +deno run --allow-env --allow-ffi --allow-read --allow-sys ... ``` ## Prerequisites @@ -48,11 +49,13 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo * macOS ARM64 * Linux ARM (glibc >= 2.31) * Linux ARM64 (glibc >= 2.26, musl >= 1.2.2) -* Linux ppc64 (glibc >= 2.31) -* Linux s390x (glibc >= 2.31) +* Linux RISC-V 64-bit (glibc >= 2.41) +* Linux ppc64 (glibc >= 2.36) +* Linux s390x (glibc >= 2.36) * Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2) * Windows x64 * Windows x86 +* Windows ARM64 (experimental, CPU with ARMv8.4 required for all features) This provides support for the JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats. @@ -74,7 +77,7 @@ npm `package-lock.json` files shared by multiple platforms can cause installatio Provides limited support via `--os`, `--cpu` and `--libc` flags. To support macOS with Intel x64 and ARM64 CPUs: -```sh +```sh frame="none" npm install --cpu=x64 --os=darwin sharp npm install --cpu=arm64 --os=darwin sharp ``` @@ -82,7 +85,7 @@ npm install --cpu=arm64 --os=darwin sharp When the cross-target is Linux, the C standard library must be specified. To support glibc (e.g. Debian) and musl (e.g. Alpine) Linux with Intel x64 CPUs: -```sh +```sh frame="none" npm install --cpu=x64 --os=linux --libc=glibc sharp npm install --cpu=x64 --os=linux --libc=musl sharp ``` @@ -93,7 +96,7 @@ Use the [supportedArchitectures](https://yarnpkg.com/configuration/yarnrc#suppor ### pnpm v8+ -Use the [supportedArchitectures](https://pnpm.io/package_json#pnpmsupportedarchitectures) configuration. +Use the [supportedArchitectures](https://pnpm.io/settings#supportedarchitectures) configuration. ## Custom libvips @@ -109,10 +112,11 @@ and on macOS when running Node.js under Rosetta. ## Building from source -This module will be compiled from source at `npm install` time when: +This module will be compiled from source when: * a globally-installed libvips is detected, or -* when the `npm install --build-from-source` flag is used. +* using `npm explore sharp -- npm run build`, or +* using the deprecated `npm run --build-from-source` at `npm install` time. The logic to detect a globally-installed libvips can be skipped by setting the `SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or @@ -128,12 +132,12 @@ Building from source requires: There is an install-time check for these dependencies. If `node-addon-api` or `node-gyp` cannot be found, try adding them via: -```sh +```sh frame="none" npm install --save node-addon-api node-gyp ``` When using `pnpm`, you may need to add `sharp` to -[onlyBuiltDependencies](https://pnpm.io/package_json#pnpmonlybuiltdependencies) +[onlyBuiltDependencies](https://pnpm.io/settings#onlybuiltdependencies) to ensure the installation script can be run. For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags @@ -151,19 +155,20 @@ Native text rendering is unsupported. [Tile-based output](/api-output#tile) is unsupported. -```sh +```sh frame="none" npm install --cpu=wasm32 sharp ``` ## FreeBSD -The `vips` package must be installed before `npm install` is run. +The `vips` package must be installed before `npm install` is run, +as well as the additional [building from source](#building-from-source) dependencies. -```sh +```sh frame="none" pkg install -y pkgconf vips ``` -```sh +```sh frame="none" cd /usr/ports/graphics/vips/ && make install clean ``` @@ -174,7 +179,7 @@ The default memory allocator on most glibc-based Linux systems processes that involve lots of small memory allocations. 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. To help avoid fragmentation and improve performance on these systems, @@ -212,7 +217,7 @@ Ensure sharp is excluded from bundling via the [externals](https://webpack.js.org/configuration/externals/) configuration. -```js +```js frame="none" externals: { 'sharp': 'commonjs sharp' } @@ -224,7 +229,7 @@ Ensure sharp is excluded from bundling via the [external](https://esbuild.github.io/api/#external) configuration. -```js +```js frame="none" buildSync({ entryPoints: ['app.js'], bundle: true, @@ -233,14 +238,14 @@ buildSync({ }) ``` -```sh +```sh frame="none" esbuild app.js --bundle --platform=node --external:sharp ``` For `serverless-esbuild`, ensure platform-specific binaries are installed via the `serverless.yml` configuration. -```yaml +```yaml frame="none" custom: esbuild: external: @@ -258,7 +263,7 @@ Ensure `sharp` is unpacked from the ASAR archive file using the [asarUnpack](https://www.electron.build/app-builder-lib.interface.platformspecificbuildoptions#asarunpack) option. -```json +```json frame="none" { "build": { "asar": true, @@ -276,7 +281,7 @@ Ensure `sharp` is unpacked from the ASAR archive file using the [unpack](https://js.electronforge.io/interfaces/_electron_forge_maker_squirrel.InternalOptions.Options.html#asar) option. -```json +```json frame="none" { "packagerConfig": { "asar": { @@ -296,7 +301,7 @@ Ensure `sharp` is excluded from bundling via the [build.rollupOptions](https://vitejs.dev/config/build-options.html) configuration. -```js +```js frame="none" import { defineConfig } from 'vite'; export default defineConfig({ diff --git a/docs/src/content/docs/performance.md b/docs/src/content/docs/performance.md index e61cfd6f8..19aead63d 100644 --- a/docs/src/content/docs/performance.md +++ b/docs/src/content/docs/performance.md @@ -2,6 +2,39 @@ 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 frame="none" +export UV_THREADPOOL_SIZE="$(lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l)" +``` + +libvips uses a shared thread pool to avoid the overhead of spawning new threads. +The size of this thread pool will grow on demand and shrink when idle. + +The default number of threads used to concurrently process each image is the same as the number of CPU cores, +except when using glibc-based Linux without jemalloc, where the default is `1` to help reduce memory fragmentation. + +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://sourceware.org/glibc/manual/latest/html_node/Memory-Allocation-Tunables.html) +environment variable before the Node.js process starts to reduce the number of memory pools. + +```sh frame="none" +export MALLOC_ARENA_MAX="2" +``` + +## Benchmark + A test to benchmark the performance of this module relative to alternatives. Greater libvips performance can be expected with caching enabled (default) @@ -9,28 +42,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. -## Contenders +### Contenders -* [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*". -* [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.0 / libvips v8.16.1 - Caching within libvips disabled to ensure a fair comparison. +- [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_". +- [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. -## Environment +### Environment -### AMD64 +#### AMD64 -* AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14) -* Ubuntu 24.10 [fad5ba7223f8](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-fad5ba7223f8d87179dfa23211d31845d47e07a474ac31ad5258afb606523c0d) -* Node.js 22.14.0 +- AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14) +- Ubuntu 25.04 +- Node.js 24.3.0 -### ARM64 +#### ARM64 -* AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4) -* Ubuntu 24.10 [133f2e05cb69](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-133f2e05cb6958c3ce7ec870fd5a864558ba780fb7062315b51a23670bff7e76) -* Node.js 22.14.0 +- AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4) +- Ubuntu 25.04 +- Node.js 24.3.0 -## Task: JPEG +### Task: JPEG Decompress a 2725x2225 JPEG image, resize to 720x588 using Lanczos 3 resampling (where available), @@ -40,29 +73,31 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead. #### Results: JPEG (AMD64) -| Module | Input | Output | Ops/sec | Speed-up | -| :----------------- | :----- | :----- | ------: | -------: | -| jimp | buffer | buffer | 2.35 | 1.0 | -| imagemagick | file | file | 10.51 | 4.5 | -| gm | buffer | buffer | 11.67 | 5.0 | -| gm | file | file | 11.75 | 5.1 | -| sharp | stream | stream | 60.72 | 25.8 | -| sharp | file | file | 62.37 | 26.5 | -| sharp | buffer | buffer | 65.15 | 27.7 | +| Package | I/O | Ops/sec | Speed-up | +| :---------- | :----- | ------: | -------: | +| jimp | buffer | 2.40 | 1.0 | +| jimp | file | 2.60 | 1.1 | +| imagemagick | file | 9.70 | 4.0 | +| gm | buffer | 11.60 | 4.8 | +| gm | file | 11.72 | 4.9 | +| sharp | stream | 59.40 | 24.8 | +| sharp | file | 62.67 | 26.1 | +| sharp | buffer | 64.42 | 26.8 | #### Results: JPEG (ARM64) -| Module | Input | Output | Ops/sec | Speed-up | -| :----------------- | :----- | :----- | ------: | -------: | -| jimp | buffer | buffer | 2.13 | 1.0 | -| imagemagick | file | file | 12.95 | 6.1 | -| gm | buffer | buffer | 13.53 | 6.4 | -| gm | file | file | 13.52 | 6.4 | -| sharp | stream | stream | 46.58 | 21.9 | -| sharp | file | file | 48.42 | 22.7 | -| sharp | buffer | buffer | 50.16 | 23.6 | +| Package | I/O | Ops/sec | Speed-up | +| :---------- | :----- | ------: | -------: | +| jimp | buffer | 2.24 | 1.0 | +| jimp | file | 2.47 | 1.1 | +| imagemagick | file | 10.42 | 4.7 | +| gm | buffer | 12.80 | 5.7 | +| gm | file | 12.88 | 5.7 | +| sharp | stream | 45.58 | 20.3 | +| sharp | file | 47.99 | 21.4 | +| sharp | buffer | 49.20 | 22.0 | -## Task: PNG +### Task: PNG Decompress a 2048x1536 RGBA PNG image, premultiply the alpha channel, @@ -72,31 +107,31 @@ and without adaptive filtering. Note: jimp does not support premultiply/unpremultiply. -### Results: PNG (AMD64) +#### Results: PNG (AMD64) -| Module | Input | Output | Ops/sec | Speed-up | -| :----------------- | :----- | :----- | ------: | -------: | -| gm | file | file | 8.66 | 1.0 | -| imagemagick | file | file | 8.79 | 1.0 | -| jimp | buffer | buffer | 11.26 | 1.3 | -| sharp | file | file | 27.93 | 3.2 | -| sharp | buffer | buffer | 28.69 | 3.3 | +| Package | I/O | Ops/sec | Speed-up | +| :---------- | :----- | ------: | -------: | +| imagemagick | file | 6.06 | 1.0 | +| gm | file | 8.44 | 1.4 | +| jimp | buffer | 10.98 | 1.8 | +| sharp | file | 28.26 | 4.7 | +| sharp | buffer | 28.70 | 4.7 | -### Results: PNG (ARM64) +#### Results: PNG (ARM64) -| Module | Input | Output | Ops/sec | Speed-up | -| :----------------- | :----- | :----- | ------: | -------: | -| gm | file | file | 9.65 | 1.0 | -| imagemagick | file | file | 9.72 | 1.0 | -| jimp | buffer | buffer | 10.68 | 1.1 | -| sharp | file | file | 23.90 | 2.5 | -| sharp | buffer | buffer | 24.48 | 2.5 | +| Package | I/O | Ops/sec | Speed-up | +| :---------- | :----- | ------: | -------: | +| imagemagick | file | 7.09 | 1.0 | +| gm | file | 8.93 | 1.3 | +| jimp | buffer | 10.28 | 1.5 | +| sharp | file | 23.81 | 3.4 | +| sharp | buffer | 24.19 | 3.4 | ## Running the benchmark test Requires Docker. -```sh +```sh frame="none" git clone https://github.com/lovell/sharp.git cd sharp/test/bench ./run-with-docker.sh diff --git a/install/build.js b/install/build.js new file mode 100644 index 000000000..2ca224586 --- /dev/null +++ b/install/build.js @@ -0,0 +1,38 @@ +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ + +const { + useGlobalLibvips, + globalLibvipsVersion, + log, + spawnRebuild, +} = require('../lib/libvips'); + +log('Attempting to build from source via node-gyp'); +log('See https://sharp.pixelplumbing.com/install#building-from-source'); + +try { + const addonApi = require('node-addon-api'); + log(`Found node-addon-api ${addonApi.version || ''}`); +} catch (_err) { + log('Please add node-addon-api to your dependencies'); + process.exit(1); +} +try { + const gyp = require('node-gyp'); + log(`Found node-gyp ${gyp().version}`); +} catch (_err) { + log('Please add node-gyp to your dependencies'); + process.exit(1); +} + +if (useGlobalLibvips(log)) { + log(`Detected globally-installed libvips v${globalLibvipsVersion()}`); +} + +const status = spawnRebuild(); +if (status !== 0) { + process.exit(status); +} diff --git a/install/check.js b/install/check.js index 0e00133ab..1cfb7d32e 100644 --- a/install/check.js +++ b/install/check.js @@ -1,39 +1,12 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ try { - const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips'); - - const buildFromSource = (msg) => { - log(msg); - log('Attempting to build from source via node-gyp'); - try { - const addonApi = require('node-addon-api'); - log(`Found node-addon-api ${addonApi.version || ''}`); - } catch (err) { - log('Please add node-addon-api to your dependencies'); - return; - } - try { - const gyp = require('node-gyp'); - log(`Found node-gyp ${gyp().version}`); - } catch (err) { - log('Please add node-gyp to your dependencies'); - return; - } - log('See https://sharp.pixelplumbing.com/install#building-from-source'); - const status = spawnRebuild(); - if (status !== 0) { - process.exit(status); - } - }; - - if (useGlobalLibvips(log)) { - buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`); - } else if (process.env.npm_config_build_from_source) { - buildFromSource('Detected --build-from-source flag'); + const { useGlobalLibvips } = require('../lib/libvips'); + if (useGlobalLibvips() || process.env.npm_config_build_from_source) { + process.exit(1); } } catch (err) { const summary = err.message.split(/\n/).slice(0, 1); diff --git a/lib/channel.js b/lib/channel.js index 46bce3d57..3c6c0b439 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const is = require('./is'); @@ -18,7 +18,7 @@ const bool = { /** * Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel. * - * See also {@link /api-operation#flatten|flatten}. + * See also {@link /api-operation/#flatten flatten}. * * @example * sharp('rgba.png') @@ -74,6 +74,8 @@ function ensureAlpha (alpha) { /** * Extract a single channel from a multi-channel image. * + * The output colourspace will be either `b-w` (8-bit) or `grey16` (16-bit). + * * @example * // green.jpg is a greyscale image containing the green channel of the input * await sharp(input) @@ -161,7 +163,7 @@ function bandbool (boolOp) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { // Public instance functions removeAlpha, diff --git a/lib/colour.js b/lib/colour.js index ad283830e..e61c248a8 100644 --- a/lib/colour.js +++ b/lib/colour.js @@ -1,9 +1,9 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const color = require('color'); +const color = require('@img/colour'); const is = require('./is'); /** @@ -69,7 +69,7 @@ function grayscale (grayscale) { * * The input image will be converted to the provided colourspace at the start of the pipeline. * All operations will use this colourspace before converting to the output colourspace, - * as defined by {@link #tocolourspace|toColourspace}. + * as defined by {@link #tocolourspace toColourspace}. * * @since 0.29.0 * @@ -80,7 +80,7 @@ function grayscale (grayscale) { * .toColourspace('srgb') * .toFile('16bpc-pipeline-to-8bpc-output.png') * - * @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774) + * @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://www.libvips.org/API/current/enum.Interpretation.html) * @returns {Sharp} * @throws {Error} Invalid parameters */ @@ -112,7 +112,7 @@ function pipelineColorspace (colorspace) { * .toColourspace('rgb16') * .toFile('16-bpp.png') * - * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794) + * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html) * @returns {Sharp} * @throws {Error} Invalid parameters */ @@ -141,7 +141,10 @@ function toColorspace (colorspace) { * @throws {Error} Invalid value */ function _getBackgroundColourOption (value) { - if (is.object(value) || is.string(value)) { + if ( + is.object(value) || + (is.string(value) && value.length >= 3 && value.length <= 200) + ) { const colour = color(value); return [ colour.red(), @@ -172,7 +175,7 @@ function _setBackgroundColourOption (key, value) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { // Public tint, diff --git a/lib/composite.js b/lib/composite.js index 98f8aefef..1c3e5e629 100644 --- a/lib/composite.js +++ b/lib/composite.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const is = require('./is'); @@ -56,7 +56,7 @@ const blend = { * `hard-light`, `soft-light`, `difference`, `exclusion`. * * More information about blend modes can be found at - * https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode + * https://www.libvips.org/API/current/enum.BlendMode.html * and https://www.cairographics.org/operators/ * * @since 0.22.0 @@ -123,8 +123,8 @@ const blend = { * @param {Number} [images[].raw.height] * @param {Number} [images[].raw.channels] * @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image. - * @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters} - * @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters} + * @param {string} [images[].failOn='warning'] - @see {@link /api-constructor/ constructor parameters} + * @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor/ constructor parameters} * @returns {Sharp} * @throws {Error} Invalid parameters */ @@ -206,7 +206,7 @@ function composite (images) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Sharp.prototype.composite = composite; Sharp.blend = blend; }; diff --git a/lib/constructor.js b/lib/constructor.js index d94900683..9aac8105c 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const util = require('node:util'); const stream = require('node:stream'); @@ -12,6 +12,10 @@ require('./sharp'); // Use NODE_DEBUG=sharp to enable libvips warnings const debuglog = util.debuglog('sharp'); +const queueListener = (queueLength) => { + Sharp.queue.emit('change', queueLength); +}; + /** * Constructor factory to create an instance of `sharp`, to which further methods are chained. * @@ -153,9 +157,6 @@ const debuglog = util.debuglog('sharp'); * @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.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 {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {number} [options.raw.width] - integral number of pixels wide. @@ -163,15 +164,17 @@ const debuglog = util.debuglog('sharp'); * @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` * 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 {number} [options.create.width] - integral number of pixels wide. * @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 {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 {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported. - * @param {number} [options.create.noise.mean] - mean of pixels in generated noise. - * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise. + * @param {number} [options.create.noise.mean=128] - Mean value of pixels in the generated noise. + * @param {number} [options.create.noise.sigma=30] - Standard deviation of pixel values in the generated noise. * @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 `LeMonde`. * @param {string} [options.text.font] - font name to render with. @@ -191,11 +194,22 @@ 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} [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 {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} * @throws {Error} Invalid parameters */ const Sharp = function (input, options) { + // biome-ignore lint/complexity/noArguments: constructor factory if (arguments.length === 1 && !is.defined(input)) { throw new Error('Invalid input'); } @@ -221,7 +235,8 @@ const Sharp = function (input, options) { angle: 0, rotationAngle: 0, rotationBackground: [0, 0, 0, 255], - rotateBeforePreExtract: false, + rotateBefore: false, + orientBefore: false, flip: false, flop: false, extendTop: 0, @@ -297,6 +312,7 @@ const Sharp = function (input, options) { withIccProfile: '', withExif: {}, withExifMerge: true, + withXmp: '', resolveWithObject: false, loop: -1, delay: [], @@ -337,10 +353,12 @@ const Sharp = function (input, options) { gifDither: 1, gifInterFrameMaxError: 0, gifInterPaletteMaxError: 3, + gifKeepDuplicateFrames: false, gifReuse: true, gifProgressive: false, tiffQuality: 80, tiffCompression: 'jpeg', + tiffBigtiff: false, tiffPredictor: 'horizontal', tiffPyramid: false, tiffMiniswhite: false, @@ -384,9 +402,7 @@ const Sharp = function (input, options) { debuglog(warning); }, // Function to notify of queue length changes - queueListener: function (queueLength) { - Sharp.queue.emit('change', queueLength); - } + queueListener }; this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); return this; diff --git a/lib/index.d.ts b/lib/index.d.ts index c87e240e6..13714c947 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -27,7 +27,7 @@ /// -import { Duplex } from 'stream'; +import type { Duplex } from 'node:stream'; //#region Constructor functions @@ -419,7 +419,7 @@ declare namespace sharp { * @returns {Sharp} */ autoOrient(): Sharp - + /** * Flip the image about the vertical Y axis. This always occurs after rotation, if any. * The use of flip implies the removal of the EXIF Orientation tag, if any. @@ -730,6 +730,20 @@ declare namespace 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. * The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space. @@ -846,6 +860,7 @@ declare namespace sharp { | JxlOptions | GifOptions | Jp2Options + | RawOptions | TiffOptions, ): Sharp; @@ -1003,12 +1018,22 @@ declare namespace sharp { pages?: number | undefined; /** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */ page?: number | undefined; - /** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default -1) */ + /** TIFF specific input options */ + 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 {@link SharpOptions.tiff} instead */ subifd?: number | undefined; - /** Level to extract from a multi-level input (OpenSlide), zero based. (optional, default 0) */ - 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. */ + /** @deprecated Use {@link SharpOptions.pdf} instead */ pdfBackground?: Colour | Color | undefined; + /** @deprecated Use {@link SharpOptions.openSlide} instead */ + level?: number | undefined; /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */ animated?: boolean | undefined; /** Describes raw pixel input image data. See raw() for pixel ordering. */ @@ -1051,6 +1076,8 @@ declare namespace sharp { 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) */ 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; @@ -1066,6 +1093,9 @@ declare namespace sharp { background: Colour | Color; /** Describes a noise to be created. */ 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 { @@ -1114,6 +1144,33 @@ declare namespace sharp { 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 { [k: string]: string; } @@ -1125,6 +1182,10 @@ declare namespace sharp { 'IFD3'?: ExifDir; } + type HeifCompression = 'av1' | 'hevc'; + + type Unit = 'inch' | 'cm'; + interface WriteableMetadata { /** Number of pixels per inch (DPI) */ density?: number | undefined; @@ -1146,13 +1207,13 @@ declare namespace sharp { /** Number value of the EXIF Orientation header, if present */ orientation?: number | undefined; /** Name of decoder used to decompress image data e.g. jpeg, png, webp, gif, svg */ - format?: keyof FormatEnum | undefined; + format: keyof FormatEnum; /** Total size of image in bytes, for Stream and Buffer input only */ size?: number | undefined; /** Number of pixels wide (EXIF orientation is not taken into consideration) */ - width?: number | undefined; + width: number; /** Number of pixels high (EXIF orientation is not taken into consideration) */ - height?: number | undefined; + height: number; /** Any changed metadata after the image orientation is applied. */ autoOrient: { /** Number of pixels wide (EXIF orientation is taken into consideration) */ @@ -1161,19 +1222,19 @@ declare namespace sharp { height: number; }; /** Name of colour space interpretation */ - space?: keyof ColourspaceEnum | undefined; + space: keyof ColourspaceEnum; /** Number of bands e.g. 3 for sRGB, 4 for CMYK */ - channels?: Channels | undefined; + channels: Channels; /** Name of pixel depth format e.g. uchar, char, ushort, float ... */ - depth?: string | undefined; + depth: keyof DepthEnum; /** Number of pixels per inch (DPI), if present */ 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 */ chromaSubsampling?: string | undefined; /** Boolean indicating whether the image is interlaced using a progressive scan */ - isProgressive?: boolean | undefined; + isProgressive: boolean; /** Boolean indicating whether the image is palette-based (GIF, PNG). */ - isPalette?: boolean | undefined; + isPalette: boolean; /** Number of bits per sample for each channel (GIF, PNG). */ bitsPerSample?: number | undefined; /** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */ @@ -1187,9 +1248,9 @@ declare namespace sharp { /** Number of the primary page in a HEIF image */ pagePrimary?: number | undefined; /** Boolean indicating the presence of an embedded ICC profile */ - hasProfile?: boolean | undefined; + hasProfile: boolean; /** Boolean indicating the presence of an alpha transparency channel */ - hasAlpha?: boolean | undefined; + hasAlpha: boolean; /** Buffer containing raw EXIF data, if present */ exif?: Buffer | undefined; /** Buffer containing raw ICC profile data, if present */ @@ -1198,10 +1259,12 @@ declare namespace sharp { iptc?: Buffer | undefined; /** Buffer containing raw XMP data, if present */ xmp?: Buffer | undefined; + /** String containing XMP data, if valid UTF-8 */ + xmpAsString?: string | undefined; /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */ tifftagPhotoshop?: Buffer | undefined; /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */ - compression?: 'av1' | 'hevc'; + compression?: HeifCompression | undefined; /** Default background colour, if present, for PNG (bKGD) and GIF images */ background?: { r: number; g: number; b: number } | { gray: number }; /** Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide */ @@ -1209,7 +1272,7 @@ declare namespace sharp { /** Number of Sub Image File Directories in an OME-TIFF image */ subifds?: number | undefined; /** The unit of resolution (density) */ - resolutionUnit?: 'inch' | 'cm' | undefined; + resolutionUnit?: Unit | undefined; /** String containing format for images loaded via *magick */ formatMagick?: string | undefined; /** Array of keyword/text pairs representing PNG text blocks, if present. */ @@ -1365,7 +1428,7 @@ declare namespace sharp { /** quality, integer 1-100 (optional, default 50) */ quality?: number | undefined; /** compression format: av1, hevc (optional, default 'av1') */ - compression?: 'av1' | 'hevc' | undefined; + compression?: HeifCompression | undefined; /** use lossless compression (optional, default false) */ lossless?: boolean | undefined; /** Level of CPU effort to reduce file size, between 0 (fastest) and 9 (slowest) (optional, default 4) */ @@ -1390,9 +1453,11 @@ declare namespace sharp { /** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */ dither?: number | undefined; /** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */ - interFrameMaxError?: number; + interFrameMaxError?: number | undefined; /** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */ - interPaletteMaxError?: number; + interPaletteMaxError?: number | undefined; + /** Keep duplicate frames in the output instead of combining them (optional, default false) */ + keepDuplicateFrames?: boolean | undefined; } interface TiffOptions extends OutputOptions { @@ -1400,6 +1465,8 @@ declare namespace sharp { quality?: number | undefined; /** Compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default 'jpeg') */ compression?: string | undefined; + /** Use BigTIFF variant (has no effect when compression is none) (optional, default false) */ + bigtiff?: boolean | undefined; /** Compression predictor options: none, horizontal, float (optional, default 'horizontal') */ predictor?: string | undefined; /** Write an image pyramid (optional, default false) */ @@ -1419,7 +1486,7 @@ declare namespace sharp { /** Write 1-bit images as miniswhite (optional, default false) */ miniswhite?: boolean | undefined; /** Resolution unit options: inch, cm (optional, default 'inch') */ - resolutionUnit?: 'inch' | 'cm' | undefined; + resolutionUnit?: Unit | undefined; } interface PngOptions extends OutputOptions { @@ -1510,7 +1577,7 @@ declare namespace sharp { interface Noise { /** type of generated noise, currently only gaussian is supported. */ - type?: 'gaussian' | undefined; + type: 'gaussian'; /** mean of pixels in generated noise. */ mean?: number | undefined; /** standard deviation of pixels in generated noise. */ @@ -1544,7 +1611,7 @@ declare namespace sharp { } interface RawOptions { - depth?: 'char' | 'uchar' | 'short' | 'ushort' | 'int' | 'uint' | 'float' | 'complex' | 'double' | 'dpcomplex'; + depth?: keyof DepthEnum; } /** 1 for grayscale, 2 for grayscale + alpha, 3 for sRGB, 4 for CMYK or RGBA */ @@ -1724,9 +1791,12 @@ declare namespace sharp { interface KernelEnum { nearest: 'nearest'; cubic: 'cubic'; + linear: 'linear'; mitchell: 'mitchell'; lanczos2: 'lanczos2'; lanczos3: 'lanczos3'; + mks2013: 'mks2013'; + mks2021: 'mks2021'; } interface PresetEnum { @@ -1745,11 +1815,38 @@ declare namespace sharp { } interface ColourspaceEnum { - multiband: string; 'b-w': string; - bw: 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; + rgb: string; + rgb16: string; + scrgb: 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'; @@ -1817,7 +1914,9 @@ declare namespace sharp { interface FormatEnum { avif: AvailableFormatInfo; + dcraw: AvailableFormatInfo; dz: AvailableFormatInfo; + exr: AvailableFormatInfo; fits: AvailableFormatInfo; gif: AvailableFormatInfo; heif: AvailableFormatInfo; @@ -1831,6 +1930,7 @@ declare namespace sharp { pdf: AvailableFormatInfo; png: AvailableFormatInfo; ppm: AvailableFormatInfo; + rad: AvailableFormatInfo; raw: AvailableFormatInfo; svg: AvailableFormatInfo; tiff: AvailableFormatInfo; diff --git a/lib/index.js b/lib/index.js index 8cfc08a80..b80191d71 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const Sharp = require('./constructor'); require('./input')(Sharp); diff --git a/lib/input.js b/lib/input.js index d9d29f3a7..48388a1d2 100644 --- a/lib/input.js +++ b/lib/input.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const is = require('./is'); const sharp = require('./sharp'); @@ -22,14 +22,27 @@ const align = { 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. * @private */ function _inputOptionsFromObject (obj) { - const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } = obj; - return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient].some(is.defined) - ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } + const params = inputStreamParameters + .filter(p => is.defined(obj[p])) + .map(p => ([p, obj[p]])); + return params.length + ? Object.fromEntries(params) : undefined; } @@ -41,7 +54,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { const inputDescriptor = { autoOrient: false, failOn: 'warning', - limitInputPixels: Math.pow(0x3FFF, 2), + limitInputPixels: 0x3FFF ** 2, ignoreIcc: false, unlimited: false, sequentialRead: true @@ -137,7 +150,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { if (is.defined(inputOptions.limitInputPixels)) { if (is.bool(inputOptions.limitInputPixels)) { inputDescriptor.limitInputPixels = inputOptions.limitInputPixels - ? Math.pow(0x3FFF, 2) + ? 0x3FFF ** 2 : 0; } else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) { inputDescriptor.limitInputPixels = inputOptions.limitInputPixels; @@ -172,8 +185,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawHeight = inputOptions.raw.height; inputDescriptor.rawChannels = inputOptions.raw.channels; - inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied; - switch (input.constructor) { case Uint8Array: case Uint8ClampedArray: @@ -207,6 +218,25 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { } else { 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) if (is.defined(inputOptions.animated)) { @@ -230,26 +260,68 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); } } - // Multi-level input (OpenSlide) - if (is.defined(inputOptions.level)) { + // OpenSlide specific options + if (is.object(inputOptions.openSlide) && is.defined(inputOptions.openSlide.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)) { - inputDescriptor.level = inputOptions.level; + inputDescriptor.openSlideLevel = inputOptions.level; } else { throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level); } } - // Sub Image File Directory (TIFF) - if (is.defined(inputOptions.subifd)) { + // TIFF specific options + if (is.object(inputOptions.tiff) && is.defined(inputOptions.tiff.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)) { - inputDescriptor.subifd = inputOptions.subifd; + inputDescriptor.tiffSubifd = inputOptions.subifd; } else { throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd); } } - // PDF background colour - if (is.defined(inputOptions.pdfBackground)) { + // SVG specific options + if (is.object(inputOptions.svg)) { + if (is.defined(inputOptions.svg.stylesheet)) { + 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 if (is.defined(inputOptions.create)) { if ( @@ -261,27 +333,44 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { inputDescriptor.createWidth = inputOptions.create.width; inputDescriptor.createHeight = inputOptions.create.height; 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 if (is.defined(inputOptions.create.noise)) { if (!is.object(inputOptions.create.noise)) { throw new Error('Expected noise to be an object'); } - if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) { + if (inputOptions.create.noise.type !== 'gaussian') { 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)) { throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels); } - inputDescriptor.createNoiseType = inputOptions.create.noise.type; - if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) { - inputDescriptor.createNoiseMean = inputOptions.create.noise.mean; - } else { - throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean); + inputDescriptor.createNoiseMean = 128; + if (is.defined(inputOptions.create.noise.mean)) { + if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) { + inputDescriptor.createNoiseMean = inputOptions.create.noise.mean; + } else { + throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean); + } } - if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) { - inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma; - } else { - throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma); + 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)) { + inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma; + } else { + throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma); + } } } else if (is.defined(inputOptions.create.background)) { if (!is.inRange(inputOptions.create.channels, 3, 4)) { @@ -424,7 +513,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { } } } else if (is.defined(inputOptions)) { - throw new Error('Invalid input options ' + inputOptions); + throw new Error(`Invalid input options ${inputOptions}`); } return inputDescriptor; } @@ -436,10 +525,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { * @param {string} encoding - unused * @param {Function} callback */ -function _write (chunk, encoding, callback) { - /* istanbul ignore else */ +function _write (chunk, _encoding, callback) { if (Array.isArray(this.options.input.buffer)) { - /* istanbul ignore else */ if (is.buffer(chunk)) { if (this.options.input.buffer.length === 0) { this.on('finish', () => { @@ -483,17 +570,17 @@ function _isStreamInput () { * such as resize or rotate. * * Dimensions in the response will respect the `page` and `pages` properties of the - * {@link /api-constructor#parameters|constructor parameters}. + * {@link /api-constructor/ constructor parameters}. * * A `Promise` is returned when `callback` is not provided. * - * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` + * - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff` * - `size`: Total size of image in bytes, for Stream and Buffer input only * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) - * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation) + * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/enum.Interpretation.html) * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat) + * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/enum.BandFormat.html) * - `density`: Number of pixels per inch (DPI), if present * - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK * - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan @@ -516,6 +603,7 @@ function _isStreamInput () { * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `iptc`: Buffer containing raw IPTC 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 * - `formatMagick`: String containing format for images loaded via *magick * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present. @@ -704,7 +792,7 @@ function stats (callback) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { // Private _inputOptionsFromObject, diff --git a/lib/is.js b/lib/is.js index a63cb2066..3ac9a1a35 100644 --- a/lib/is.js +++ b/lib/is.js @@ -1,61 +1,49 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ /** * Is this value defined and not null? * @private */ -const defined = function (val) { - return typeof val !== 'undefined' && val !== null; -}; +const defined = (val) => typeof val !== 'undefined' && val !== null; /** * Is this value an object? * @private */ -const object = function (val) { - return typeof val === 'object'; -}; +const object = (val) => typeof val === 'object'; /** * Is this value a plain object? * @private */ -const plainObject = function (val) { - return Object.prototype.toString.call(val) === '[object Object]'; -}; +const plainObject = (val) => Object.prototype.toString.call(val) === '[object Object]'; /** * Is this value a function? * @private */ -const fn = function (val) { - return typeof val === 'function'; -}; +const fn = (val) => typeof val === 'function'; /** * Is this value a boolean? * @private */ -const bool = function (val) { - return typeof val === 'boolean'; -}; +const bool = (val) => typeof val === 'boolean'; /** * Is this value a Buffer object? * @private */ -const buffer = function (val) { - return val instanceof Buffer; -}; +const buffer = (val) => val instanceof Buffer; /** * Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray? * @private */ -const typedArray = function (val) { +const typedArray = (val) => { if (defined(val)) { switch (val.constructor) { case Uint8Array: @@ -78,49 +66,37 @@ const typedArray = function (val) { * Is this value an ArrayBuffer object? * @private */ -const arrayBuffer = function (val) { - return val instanceof ArrayBuffer; -}; +const arrayBuffer = (val) => val instanceof ArrayBuffer; /** * Is this value a non-empty string? * @private */ -const string = function (val) { - return typeof val === 'string' && val.length > 0; -}; +const string = (val) => typeof val === 'string' && val.length > 0; /** * Is this value a real number? * @private */ -const number = function (val) { - return typeof val === 'number' && !Number.isNaN(val); -}; +const number = (val) => typeof val === 'number' && !Number.isNaN(val); /** * Is this value an integer? * @private */ -const integer = function (val) { - return Number.isInteger(val); -}; +const integer = (val) => Number.isInteger(val); /** * Is this value within an inclusive given range? * @private */ -const inRange = function (val, min, max) { - return val >= min && val <= max; -}; +const inRange = (val, min, max) => val >= min && val <= max; /** * Is this value within the elements of an array? * @private */ -const inArray = function (val, list) { - return list.includes(val); -}; +const inArray = (val, list) => list.includes(val); /** * Create an Error with a message relating to an invalid parameter. @@ -131,11 +107,9 @@ const inArray = function (val, list) { * @returns {Error} Containing the formatted message. * @private */ -const invalidParameterError = function (name, expected, actual) { - return new Error( +const invalidParameterError = (name, expected, actual) => new Error( `Expected ${expected} for ${name} but received ${actual} of type ${typeof actual}` ); -}; /** * Ensures an Error from C++ contains a JS stack. @@ -145,7 +119,7 @@ const invalidParameterError = function (name, expected, actual) { * @returns {Error} Error with message and stack. * @private */ -const nativeError = function (native, context) { +const nativeError = (native, context) => { context.message = native.message; return context; }; diff --git a/lib/libvips.js b/lib/libvips.js index 9a5639750..881dc5c13 100644 --- a/lib/libvips.js +++ b/lib/libvips.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const { spawnSync } = require('node:child_process'); const { createHash } = require('node:crypto'); @@ -12,15 +12,15 @@ const detectLibc = require('detect-libc'); const { config, engines, optionalDependencies } = require('../package.json'); -const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */ - config.libvips; +/* node:coverage ignore next */ +const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || config.libvips; const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version; const prebuiltPlatforms = [ 'darwin-arm64', 'darwin-x64', - 'linux-arm', 'linux-arm64', 'linux-s390x', 'linux-x64', + 'linux-arm', 'linux-arm64', 'linux-ppc64', 'linux-riscv64', 'linux-s390x', 'linux-x64', 'linuxmusl-arm64', 'linuxmusl-x64', - 'win32-ia32', 'win32-x64' + 'win32-arm64', 'win32-ia32', 'win32-x64' ]; const spawnSyncOptions = { @@ -36,17 +36,16 @@ const log = (item) => { } }; -/* istanbul ignore next */ +/* node:coverage ignore next */ const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : ''; const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`; -/* istanbul ignore next */ const buildPlatformArch = () => { + /* node:coverage ignore next 3 */ if (isEmscripten()) { return 'wasm32'; } - /* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */ const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env; const libc = typeof npm_config_libc === 'string' ? npm_config_libc : runtimeLibc(); return `${npm_config_platform || process.platform}${libc}-${npm_config_arch || process.arch}`; @@ -56,19 +55,19 @@ const buildSharpLibvipsIncludeDir = () => { try { return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/include`); } catch { + /* node:coverage ignore next 5 */ try { return require('@img/sharp-libvips-dev/include'); } catch {} } - /* istanbul ignore next */ return ''; }; const buildSharpLibvipsCPlusPlusDir = () => { + /* node:coverage ignore next 4 */ try { return require('@img/sharp-libvips-dev/cplusplus'); } catch {} - /* istanbul ignore next */ return ''; }; @@ -76,16 +75,17 @@ const buildSharpLibvipsLibDir = () => { try { return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/lib`); } catch { + /* node:coverage ignore next 5 */ try { return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`); } catch {} } - /* istanbul ignore next */ return ''; }; +/* node:coverage disable */ + const isUnsupportedNodeRuntime = () => { - /* istanbul ignore next */ if (process.release?.name === 'node' && process.versions) { if (!semverSatisfies(process.versions.node, engines.node)) { return { found: process.versions.node, expected: engines.node }; @@ -93,14 +93,12 @@ const isUnsupportedNodeRuntime = () => { } }; -/* istanbul ignore next */ const isEmscripten = () => { const { CC } = process.env; - return Boolean(CC && CC.endsWith('/emcc')); + return Boolean(CC?.endsWith('/emcc')); }; const isRosetta = () => { - /* istanbul ignore next */ if (process.platform === 'darwin' && process.arch === 'x64') { const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout; return (translated || '').trim() === 'sysctl.proc_translated: 1'; @@ -108,6 +106,8 @@ const isRosetta = () => { return false; }; +/* node:coverage enable */ + const sha512 = (s) => createHash('sha512').update(s).digest('hex'); const yarnLocator = () => { @@ -121,7 +121,8 @@ const yarnLocator = () => { return ''; }; -/* istanbul ignore next */ +/* node:coverage disable */ + const spawnRebuild = () => spawnSync(`node-gyp rebuild --directory=src ${isEmscripten() ? '--nodedir=emscripten' : ''}`, { ...spawnSyncOptions, @@ -137,16 +138,17 @@ const globalLibvipsVersion = () => { PKG_CONFIG_PATH: pkgConfigPath() } }).stdout; - /* istanbul ignore next */ return (globalLibvipsVersion || '').trim(); } else { return ''; } }; -/* istanbul ignore next */ +/* node:coverage enable */ + const pkgConfigPath = () => { if (process.platform !== 'win32') { + /* node:coverage ignore next 4 */ const brewPkgConfigPath = spawnSync( 'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2', spawnSyncOptions @@ -178,13 +180,13 @@ const useGlobalLibvips = (logger) => { if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) { return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS', logger); } - /* istanbul ignore next */ + /* node:coverage ignore next 3 */ if (isRosetta()) { return skipSearch(false, 'Rosetta', logger); } const globalVipsVersion = globalLibvipsVersion(); - return !!globalVipsVersion && /* istanbul ignore next */ - semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion); + /* node:coverage ignore next */ + return !!globalVipsVersion && semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion); }; module.exports = { diff --git a/lib/operation.js b/lib/operation.js index f76f65dc3..ebbf54e9c 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const is = require('./is'); @@ -239,7 +239,7 @@ function affine (matrix, options) { * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. * Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available. * - * See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation. + * See {@link https://www.libvips.org/API/current/method.Image.sharpen.html libvips sharpen} operation. * * @example * const data = await sharp(input).sharpen().toBuffer(); @@ -485,7 +485,7 @@ function erode (width) { /** * Merge alpha transparency channel, if any, with a background, then remove the alpha channel. * - * See also {@link /api-channel#removealpha|removeAlpha}. + * See also {@link /api-channel#removealpha removeAlpha}. * * @example * await sharp(rgbaInput) @@ -660,7 +660,7 @@ function normalize (options) { /** * Perform contrast limiting adaptive histogram equalization - * {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}. + * {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE CLAHE}. * * This will, in general, enhance the clarity of the image by bringing out darker details. * @@ -742,9 +742,7 @@ function convolve (kernel) { } // Default scale is sum of kernel values if (!is.integer(kernel.scale)) { - kernel.scale = kernel.kernel.reduce(function (a, b) { - return a + b; - }, 0); + kernel.scale = kernel.kernel.reduce((a, b) => a + b, 0); } // Clip scale to a minimum value of 1 if (kernel.scale < 1) { @@ -989,7 +987,7 @@ function modulate (options) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { autoOrient, rotate, diff --git a/lib/output.js b/lib/output.js index 5c2bdf295..27a6ac470 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const path = require('node:path'); const is = require('./is'); @@ -43,7 +43,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math * Note that raw pixel data is only supported for buffer output. * * By default all metadata will be removed, which includes EXIF-based orientation. - * See {@link #withmetadata|withMetadata} for control over this. + * See {@link #withmetadata withMetadata} for control over this. * * The caller is responsible for ensuring directory structures and permissions exist. * @@ -97,12 +97,12 @@ function toFile (fileOut, callback) { * Write output to a Buffer. * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported. * - * Use {@link #toformat|toFormat} or one of the format-specific functions such as {@link jpeg}, {@link png} etc. to set the output format. + * Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format. * * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output. * * By default all metadata will be removed, which includes EXIF-based orientation. - * See {@link #withmetadata|withMetadata} for control over this. + * See {@link #withmetadata withMetadata} for control over this. * * `callback`, if present, gets three arguments `(err, data, info)` where: * - `err` is an error, if any. @@ -256,7 +256,7 @@ function withExifMerge (exif) { /** * Keep ICC profile from the input image in the output image. * - * Where necessary, will attempt to convert the output colour space to match the profile. + * When input and output colour spaces differ, use with {@link /api-colour/#tocolourspace toColourspace} and optionally {@link /api-colour/#pipelinecolourspace pipelineColourspace}. * * @since 0.33.0 * @@ -265,6 +265,13 @@ function withExifMerge (exif) { * .keepIccProfile() * .toBuffer(); * + * @example + * const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile) + * .pipelineColourspace('cmyk') + * .toColourspace('cmyk') + * .keepIccProfile() + * .toBuffer(); + * * @returns {Sharp} */ function keepIccProfile () { @@ -312,6 +319,59 @@ function withIccProfile (icc, options) { 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 = ` + * + * + * + * + * John Doe + * + * + * `; + * + * 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. * @@ -512,7 +572,7 @@ function jpeg (options) { * Set `palette` to `true` for slower, indexed PNG output. * * For 16 bits per pixel output, convert to `rgb16` via - * {@link /api-colour#tocolourspace|toColourspace}. + * {@link /api-colour/#tocolourspace toColourspace}. * * @example * // Convert any input to full colour PNG output @@ -729,6 +789,7 @@ function webp (options) { * @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.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|number[]} [options.delay] - delay(s) between animation frames (in milliseconds) * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format @@ -779,18 +840,24 @@ function gif (options) { 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); return this._updateFormatOut('gif', options); } -/* istanbul ignore next */ /** * Use these JP2 options for output image. * * Requires libvips compiled with support for OpenJPEG. * The prebuilt binaries do not include this - see - * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}. + * {@link /install#custom-libvips installing a custom libvips}. * * @example * // Convert any input to lossless JP2 output @@ -819,6 +886,7 @@ function gif (options) { * @throws {Error} Invalid options */ function jp2 (options) { + /* node:coverage ignore next 41 */ if (!this.constructor.format.jp2k.output.buffer) { throw errJp2Save(); } @@ -898,7 +966,7 @@ function trySetAnimationOptions (source, target) { /** * Use these TIFF options for output image. * - * The `density` can be set in pixels/inch via {@link #withmetadata|withMetadata} + * The `density` can be set in pixels/inch via {@link #withmetadata withMetadata} * instead of providing `xres` and `yres` in pixels/mm. * * @example @@ -915,6 +983,7 @@ function trySetAnimationOptions (source, target) { * @param {number} [options.quality=80] - quality, integer 1-100 * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k + * @param {boolean} [options.bigtiff=false] - use BigTIFF variant (has no effect when compression is none) * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.tile=false] - write a tiled tiff @@ -993,6 +1062,10 @@ function tiff (options) { throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression); } } + // bigtiff + if (is.defined(options.bigtiff)) { + this._setBooleanOption('tiffBigtiff', options.bigtiff); + } // predictor if (is.defined(options.predictor)) { if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) { @@ -1019,6 +1092,9 @@ function tiff (options) { * AVIF image sequences are not supported. * Prebuilt binaries support a bitdepth of 8 only. * + * This feature is experimental on the Windows ARM64 platform + * and requires a CPU with ARM64v8.4 or later. + * * @example * const data = await sharp(input) * .avif({ effort: 2 }) @@ -1125,7 +1201,7 @@ function heif (options) { * * Requires libvips compiled with support for libjxl. * The prebuilt binaries do not include this - see - * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}. + * {@link /install/#custom-libvips installing a custom libvips}. * * @since 0.31.3 * @@ -1438,7 +1514,6 @@ function _setBooleanOption (key, val) { * @private */ function _read () { - /* istanbul ignore else */ if (!this.options.streamOut) { this.options.streamOut = true; const stack = Error(); @@ -1555,7 +1630,7 @@ function _pipeline (callback, stack) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { // Public toFile, @@ -1565,6 +1640,8 @@ module.exports = function (Sharp) { withExifMerge, keepIccProfile, withIccProfile, + keepXmp, + withXmp, keepMetadata, withMetadata, toFormat, diff --git a/lib/resize.js b/lib/resize.js index 66cfd8719..544fbba3a 100644 --- a/lib/resize.js +++ b/lib/resize.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const is = require('./is'); @@ -107,7 +107,7 @@ const mapFitToCanvas = { * @private */ function isRotationExpected (options) { - return (options.angle % 360) !== 0 || options.input.autoOrient === true || options.rotationAngle !== 0; + return (options.angle % 360) !== 0 || options.rotationAngle !== 0; } /** @@ -150,6 +150,8 @@ function isResizeExpected (options) { * - `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`. * - `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. * Downsampling kernels without a matching upsampling interpolator map to `cubic`. @@ -341,7 +343,7 @@ function resize (widthOrOptions, height, options) { } } if (isRotationExpected(this.options) && isResizeExpected(this.options)) { - this.options.rotateBeforePreExtract = true; + this.options.rotateBefore = true; } return this; } @@ -488,9 +490,12 @@ function extract (options) { // Ensure existing rotation occurs before pre-resize extraction if (isRotationExpected(this.options) && !isResizeExpected(this.options)) { if (this.options.widthPre === -1 || this.options.widthPost === -1) { - this.options.rotateBeforePreExtract = true; + this.options.rotateBefore = true; } } + if (this.options.input.autoOrient) { + this.options.orientBefore = true; + } return this; } @@ -564,7 +569,7 @@ function trim (options) { } } if (isRotationExpected(this.options)) { - this.options.rotateBeforePreExtract = true; + this.options.rotateBefore = true; } return this; } @@ -574,7 +579,7 @@ function trim (options) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Object.assign(Sharp.prototype, { resize, extend, diff --git a/lib/sharp.js b/lib/sharp.js index 78853e95f..1081c9314 100644 --- a/lib/sharp.js +++ b/lib/sharp.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ // Inspects the runtime environment and exports the relevant sharp.node binary @@ -17,6 +17,8 @@ const paths = [ '@img/sharp-wasm32/sharp.node' ]; +/* node:coverage disable */ + let path, sharp; const errors = []; for (path of paths) { @@ -24,12 +26,10 @@ for (path of paths) { sharp = require(path); break; } catch (err) { - /* istanbul ignore next */ errors.push(err); } } -/* istanbul ignore next */ if (sharp && path.startsWith('@img/sharp-linux-x64') && !sharp._isUsingX64V2()) { const err = new Error('Prebuilt binaries for linux-x64 require v2 microarchitecture'); err.code = 'Unsupported CPU'; @@ -37,7 +37,6 @@ if (sharp && path.startsWith('@img/sharp-linux-x64') && !sharp._isUsingX64V2()) sharp = null; } -/* istanbul ignore next */ if (sharp) { module.exports = sharp; } else { @@ -88,7 +87,7 @@ if (sharp) { ` Found ${libcFound}`, ` Requires ${libcRequires}` ); - } catch (errEngines) {} + } catch (_errEngines) {} } if (isLinux && /\/snap\/core[0-9]{2}/.test(messages)) { help.push( diff --git a/lib/utility.js b/lib/utility.js index 3c7286fb9..c0ad39f86 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -1,7 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const events = require('node:events'); const detectLibc = require('detect-libc'); @@ -57,7 +57,7 @@ const interpolators = { let versions = { vips: libvipsVersion.semver }; -/* istanbul ignore next */ +/* node:coverage ignore next 15 */ if (!libvipsVersion.isGlobal) { if (!libvipsVersion.isWasm) { try { @@ -75,7 +75,7 @@ if (!libvipsVersion.isGlobal) { } versions.sharp = require('../package.json').version; -/* istanbul ignore next */ +/* node:coverage ignore next 5 */ if (versions.heif && format.heif) { // Prebuilt binaries provide AV1 format.heif.input.fileSuffix = ['.avif']; @@ -135,15 +135,9 @@ cache(true); * e.g. libaom manages its own 4 threads when encoding AVIF images, * and these are independent of the value set here. * - * The maximum number of images that sharp can process in parallel - * 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. + * :::note + * Further {@link /performance/ control over performance} is available. + * ::: * * @example * const threads = sharp.concurrency(); // 4 @@ -156,7 +150,7 @@ cache(true); function concurrency (concurrency) { return sharp.concurrency(is.integer(concurrency) ? concurrency : null); } -/* istanbul ignore next */ +/* node:coverage ignore next 7 */ if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) { // Reduce default concurrency to 1 when using glibc memory allocator sharp.concurrency(1); @@ -283,7 +277,7 @@ function unblock (options) { * @module Sharp * @private */ -module.exports = function (Sharp) { +module.exports = (Sharp) => { Sharp.cache = cache; Sharp.concurrency = concurrency; Sharp.counters = counters; diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json index 69e1c310b..df6b34a92 100644 --- a/npm/darwin-arm64/package.json +++ b/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-darwin-arm64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with macOS 64-bit ARM", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.1.0" + "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "files": [ "lib" diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json index ff6285950..147d109dd 100644 --- a/npm/darwin-x64/package.json +++ b/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-darwin-x64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with macOS x64", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.1.0" + "@img/sharp-libvips-darwin-x64": "1.2.4" }, "files": [ "lib" diff --git a/npm/from-github-release.js b/npm/from-github-release.js deleted file mode 100644 index 84f9fb9b3..000000000 --- a/npm/from-github-release.js +++ /dev/null @@ -1,73 +0,0 @@ -// 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); - } -}); diff --git a/npm/from-local-build.js b/npm/from-local-build.js index ad7593ece..fc35bd2ed 100644 --- a/npm/from-local-build.js +++ b/npm/from-local-build.js @@ -1,26 +1,64 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; +// Populate the npm package for the current platform with the local build -// Populate contents of a single npm/sharpen-sharp- package -// with the local/CI build directory for local/CI prebuild testing - -const fs = require('node:fs'); -const path = require('node:path'); +const { copyFileSync, cpSync, readFileSync, writeFileSync, appendFileSync } = require('node:fs'); +const { basename, join } = require('node:path'); 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 dest = path.join(__dirname, platform); +const destDir = join(__dirname, platform); +console.log(`Populating npm package for platform: ${platform}`); -// Use same config as prebuild to copy binary files -const release = path.join(__dirname, '..', 'src', 'build', 'Release'); -const prebuildrc = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.prebuildrc'), 'utf8')); -const include = new RegExp(prebuildrc['include-regex'], 'i'); -fs.cpSync(release, path.join(dest, 'lib'), { +// Copy binaries +const releaseDir = join(__dirname, '..', 'src', 'build', 'Release'); +const libDir = join(destDir, 'lib'); +cpSync(releaseDir, libDir, { recursive: true, filter: (file) => { - const name = path.basename(file); - return name === 'Release' || include.test(name); + const name = basename(file); + return name === 'Release' || + (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); +} diff --git a/npm/linux-arm/package.json b/npm/linux-arm/package.json index d8565b565..cd4d5a2b6 100644 --- a/npm/linux-arm/package.json +++ b/npm/linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linux-arm", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.1.0" + "@img/sharp-libvips-linux-arm": "1.2.4" }, "files": [ "lib" diff --git a/npm/linux-arm64/package.json b/npm/linux-arm64/package.json index 3264e375b..b373bb920 100644 --- a/npm/linux-arm64/package.json +++ b/npm/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linux-arm64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.1.0" + "@img/sharp-libvips-linux-arm64": "1.2.4" }, "files": [ "lib" diff --git a/npm/linux-ppc64/package.json b/npm/linux-ppc64/package.json index f74d5a39c..db2b62c01 100644 --- a/npm/linux-ppc64/package.json +++ b/npm/linux-ppc64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linux-ppc64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (glibc) ppc64", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.1.0" + "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "files": [ "lib" @@ -32,7 +32,7 @@ "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "config": { - "glibc": ">=2.31" + "glibc": ">=2.36" }, "os": [ "linux" diff --git a/npm/linux-riscv64/package.json b/npm/linux-riscv64/package.json new file mode 100644 index 000000000..9f0e95204 --- /dev/null +++ b/npm/linux-riscv64/package.json @@ -0,0 +1,46 @@ +{ + "name": "@img/sharp-linux-riscv64", + "version": "0.34.5", + "description": "Prebuilt sharp for use with Linux (glibc) RISC-V 64-bit", + "author": "Lovell Fuller ", + "homepage": "https://sharp.pixelplumbing.com", + "repository": { + "type": "git", + "url": "git+https://github.com/lovell/sharp.git", + "directory": "npm/linux-riscv64" + }, + "license": "Apache-2.0", + "funding": { + "url": "https://opencollective.com/libvips" + }, + "preferUnplugged": true, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "type": "commonjs", + "exports": { + "./sharp.node": "./lib/sharp-linux-riscv64.node", + "./package": "./package.json" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "config": { + "glibc": ">=2.41" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ], + "cpu": [ + "riscv64" + ] +} diff --git a/npm/linux-s390x/package.json b/npm/linux-s390x/package.json index 98af5a923..423692edd 100644 --- a/npm/linux-s390x/package.json +++ b/npm/linux-s390x/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linux-s390x", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (glibc) s390x", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.1.0" + "@img/sharp-libvips-linux-s390x": "1.2.4" }, "files": [ "lib" @@ -32,7 +32,7 @@ "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "config": { - "glibc": ">=2.31" + "glibc": ">=2.36" }, "os": [ "linux" diff --git a/npm/linux-x64/package.json b/npm/linux-x64/package.json index e5ce63c70..95a8a035f 100644 --- a/npm/linux-x64/package.json +++ b/npm/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linux-x64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (glibc) x64", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.1.0" + "@img/sharp-libvips-linux-x64": "1.2.4" }, "files": [ "lib" diff --git a/npm/linuxmusl-arm64/package.json b/npm/linuxmusl-arm64/package.json index 9e3a86f6e..5e214bf18 100644 --- a/npm/linuxmusl-arm64/package.json +++ b/npm/linuxmusl-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linuxmusl-arm64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "files": [ "lib" diff --git a/npm/linuxmusl-x64/package.json b/npm/linuxmusl-x64/package.json index 8c253f578..8be92db0d 100644 --- a/npm/linuxmusl-x64/package.json +++ b/npm/linuxmusl-x64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-linuxmusl-x64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Linux (musl) x64", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -15,7 +15,7 @@ }, "preferUnplugged": true, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "files": [ "lib" diff --git a/npm/package.json b/npm/package.json index fd921905e..773daa94a 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp", - "version": "0.34.1", + "version": "0.34.5", "private": "true", "workspaces": [ "darwin-arm64", @@ -8,11 +8,13 @@ "linux-arm", "linux-arm64", "linux-ppc64", + "linux-riscv64", "linux-s390x", "linux-x64", "linuxmusl-arm64", "linuxmusl-x64", "wasm32", + "win32-arm64", "win32-ia32", "win32-x64" ] diff --git a/npm/release-notes.js b/npm/release-notes.js new file mode 100644 index 000000000..13cc18772 --- /dev/null +++ b/npm/release-notes.js @@ -0,0 +1,9 @@ +const { readFileSync, writeFileSync } = require('node:fs'); + +const { version } = require('./package.json'); +const versionWithoutPreRelease = version.replace(/-rc\.\d+$/, ''); + +const markdown = readFileSync(`./docs/src/content/docs/changelog/v${versionWithoutPreRelease}.md`, 'utf8'); +const markdownWithoutFrontmatter = markdown.replace(/---\n.*?\n---\n+/s, ''); + +writeFileSync('./release-notes.md', markdownWithoutFrontmatter); diff --git a/npm/wasm32/package.json b/npm/wasm32/package.json index 0bc0d4eb4..d5775bec3 100644 --- a/npm/wasm32/package.json +++ b/npm/wasm32/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-wasm32", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with wasm32", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", @@ -31,7 +31,7 @@ "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "dependencies": { - "@emnapi/runtime": "^1.4.0" + "@emnapi/runtime": "^1.7.0" }, "cpu": [ "wasm32" diff --git a/npm/win32-arm64/package.json b/npm/win32-arm64/package.json new file mode 100644 index 000000000..651f420ea --- /dev/null +++ b/npm/win32-arm64/package.json @@ -0,0 +1,39 @@ +{ + "name": "@img/sharp-win32-arm64", + "version": "0.34.5", + "description": "Prebuilt sharp for use with Windows 64-bit ARM", + "author": "Lovell Fuller ", + "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" + ] +} diff --git a/npm/win32-ia32/package.json b/npm/win32-ia32/package.json index 641d90fa2..21c6dbba2 100644 --- a/npm/win32-ia32/package.json +++ b/npm/win32-ia32/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-win32-ia32", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Windows x86 (32-bit)", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", diff --git a/npm/win32-x64/package.json b/npm/win32-x64/package.json index 2de9d2e13..8b867b5e2 100644 --- a/npm/win32-x64/package.json +++ b/npm/win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@img/sharp-win32-x64", - "version": "0.34.1", + "version": "0.34.5", "description": "Prebuilt sharp for use with Windows x64", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", diff --git a/package.json b/package.json index 6970f9ec1..0c0d00988 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sharp", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images", - "version": "0.34.1", + "version": "0.34.5", "author": "Lovell Fuller ", "homepage": "https://sharp.pixelplumbing.com", "contributors": [ @@ -92,16 +92,18 @@ "Don Denton " ], "scripts": { - "install": "node install/check", + "build": "node install/build.js", + "install": "node install/check.js || npm run build", "clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*", - "test": "npm run test-lint && npm run test-unit && npm run test-licensing && npm run test-types", - "test-lint": "semistandard && cpplint", - "test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha", - "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;LGPL-3.0-or-later;MIT\"", + "test": "npm run lint && npm run test-unit", + "lint": "npm run lint-cpp && npm run lint-js && npm run lint-types", + "lint-cpp": "cpplint --quiet src/*.h src/*.cc", + "lint-js": "biome lint", + "lint-types": "tsd --files ./test/types/sharp.test-d.ts", "test-leak": "./test/leak/leak.sh", - "test-types": "tsd", - "package-from-local-build": "node npm/from-local-build", - "package-from-github-release": "node npm/from-github-release", + "test-unit": "node --experimental-test-coverage test/unit.mjs", + "package-from-local-build": "node npm/from-local-build.js", + "package-release-notes": "node npm/release-notes.js", "docs-build": "node docs/build.mjs", "docs-serve": "cd docs && npm start", "docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" @@ -137,86 +139,64 @@ "vips" ], "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.7.1" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.1", - "@img/sharp-darwin-x64": "0.34.1", - "@img/sharp-libvips-darwin-arm64": "1.1.0", - "@img/sharp-libvips-darwin-x64": "1.1.0", - "@img/sharp-libvips-linux-arm": "1.1.0", - "@img/sharp-libvips-linux-arm64": "1.1.0", - "@img/sharp-libvips-linux-ppc64": "1.1.0", - "@img/sharp-libvips-linux-s390x": "1.1.0", - "@img/sharp-libvips-linux-x64": "1.1.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", - "@img/sharp-libvips-linuxmusl-x64": "1.1.0", - "@img/sharp-linux-arm": "0.34.1", - "@img/sharp-linux-arm64": "0.34.1", - "@img/sharp-linux-s390x": "0.34.1", - "@img/sharp-linux-x64": "0.34.1", - "@img/sharp-linuxmusl-arm64": "0.34.1", - "@img/sharp-linuxmusl-x64": "0.34.1", - "@img/sharp-wasm32": "0.34.1", - "@img/sharp-win32-ia32": "0.34.1", - "@img/sharp-win32-x64": "0.34.1" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" }, "devDependencies": { - "@emnapi/runtime": "^1.4.0", - "@img/sharp-libvips-dev": "1.1.0", - "@img/sharp-libvips-dev-wasm32": "1.1.0", - "@img/sharp-libvips-win32-ia32": "1.1.0", - "@img/sharp-libvips-win32-x64": "1.1.0", + "@biomejs/biome": "^2.3.4", + "@cpplint/cli": "^0.1.0", + "@emnapi/runtime": "^1.7.0", + "@img/sharp-libvips-dev": "1.2.4", + "@img/sharp-libvips-dev-wasm32": "1.2.4", + "@img/sharp-libvips-win32-arm64": "1.2.4", + "@img/sharp-libvips-win32-ia32": "1.2.4", + "@img/sharp-libvips-win32-x64": "1.2.4", "@types/node": "*", - "cc": "^3.0.1", - "emnapi": "^1.4.0", + "emnapi": "^1.7.0", "exif-reader": "^2.0.2", "extract-zip": "^2.0.1", "icc": "^3.0.0", - "jsdoc-to-markdown": "^9.1.1", - "license-checker": "^25.0.1", - "mocha": "^11.1.0", - "node-addon-api": "^8.3.1", - "nyc": "^17.1.0", - "prebuild": "^13.0.1", - "semistandard": "^17.0.0", - "tar-fs": "^3.0.8", - "tsd": "^0.31.2" + "jsdoc-to-markdown": "^9.1.3", + "node-addon-api": "^8.5.0", + "node-gyp": "^11.5.0", + "tar-fs": "^3.1.1", + "tsd": "^0.33.0" }, "license": "Apache-2.0", "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "config": { - "libvips": ">=8.16.1" + "libvips": ">=8.17.3" }, "funding": { "url": "https://opencollective.com/libvips" - }, - "binary": { - "napi_versions": [ - 9 - ] - }, - "semistandard": { - "env": [ - "mocha" - ] - }, - "cc": { - "linelength": "120", - "filter": [ - "build/include" - ] - }, - "nyc": { - "include": [ - "lib" - ] - }, - "tsd": { - "directory": "test/types/" } } diff --git a/src/CPPLINT.cfg b/src/CPPLINT.cfg new file mode 100644 index 000000000..7a643b075 --- /dev/null +++ b/src/CPPLINT.cfg @@ -0,0 +1,10 @@ +set noparent + +linelength=120 + +filter=-build/include +filter=+build/include_alpha +filter=+build/include_subdir +filter=+build/include_what_you_use + +filter=-whitespace/indent_namespace diff --git a/src/binding.gyp b/src/binding.gyp index 0fbf515ea..2040cde5b 100644 --- a/src/binding.gyp +++ b/src/binding.gyp @@ -120,7 +120,7 @@ 'conditions': [ ['use_global_libvips == "true"', { # Use pkg-config for include and lib - 'include_dirs': [' #include +#include +#include +#include #include -#include +#include +#include #include -#include -#include -#include // NOLINT(build/c++11) #include #include -#include "common.h" +#include "./common.h" using vips::VImage; @@ -93,6 +97,7 @@ namespace sharp { descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied"); + descriptor->rawPageHeight = AttrAsUint32(input, "rawPageHeight"); } // Multi-page input (GIF, TIFF, PDF) if (HasAttr(input, "pages")) { @@ -101,23 +106,35 @@ namespace sharp { if (HasAttr(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) - if (HasAttr(input, "level")) { - descriptor->level = AttrAsUint32(input, "level"); + if (HasAttr(input, "openSlideLevel")) { + descriptor->openSlideLevel = AttrAsUint32(input, "openSlideLevel"); } // subIFD (OME-TIFF) if (HasAttr(input, "subifd")) { - descriptor->subifd = AttrAsInt32(input, "subifd"); + descriptor->tiffSubifd = AttrAsInt32(input, "tiffSubifd"); } // // PDF background color if (HasAttr(input, "pdfBackground")) { descriptor->pdfBackground = AttrAsVectorOfDouble(input, "pdfBackground"); } + // Use JPEG 2000 oneshot mode? + if (HasAttr(input, "jp2Oneshot")) { + descriptor->jp2Oneshot = AttrAsBool(input, "jp2Oneshot"); + } // Create new image if (HasAttr(input, "createChannels")) { descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createWidth = AttrAsUint32(input, "createWidth"); descriptor->createHeight = AttrAsUint32(input, "createHeight"); + descriptor->createPageHeight = AttrAsUint32(input, "createPageHeight"); if (HasAttr(input, "createNoiseType")) { descriptor->createNoiseType = AttrAsStr(input, "createNoiseType"); descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean"); @@ -271,6 +288,7 @@ namespace sharp { case ImageType::EXR: id = "exr"; break; case ImageType::JXL: id = "jxl"; break; case ImageType::RAD: id = "rad"; break; + case ImageType::DCRAW: id = "dcraw"; break; case ImageType::VIPS: id = "vips"; break; case ImageType::RAW: id = "raw"; break; case ImageType::UNKNOWN: id = "unknown"; break; @@ -319,6 +337,8 @@ namespace sharp { { "VipsForeignLoadJxlBuffer", ImageType::JXL }, { "VipsForeignLoadRadFile", ImageType::RAD }, { "VipsForeignLoadRadBuffer", ImageType::RAD }, + { "VipsForeignLoadDcRawFile", ImageType::DCRAW }, + { "VipsForeignLoadDcRawBuffer", ImageType::DCRAW }, { "VipsForeignLoadVips", ImageType::VIPS }, { "VipsForeignLoadVipsFile", ImageType::VIPS }, { "VipsForeignLoadRaw", ImageType::RAW } @@ -380,9 +400,52 @@ namespace sharp { imageType == ImageType::JPEG || imageType == ImageType::PNG || imageType == ImageType::SVG || + imageType == ImageType::TIFF || 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("subifd", descriptor->tiffSubifd); + break; + case ImageType::PDF: + option->set("dpi", descriptor->density) + ->set("background", descriptor->pdfBackground); + break; + case ImageType::OPENSLIDE: + option->set("level", 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) */ @@ -400,6 +463,10 @@ namespace sharp { } else { 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(descriptor->rawHeight / descriptor->rawPageHeight)); + } if (descriptor->rawPremultiplied) { image = image.unpremultiply(); } @@ -409,31 +476,7 @@ namespace sharp { imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength); if (imageType != ImageType::UNKNOWN) { try { - 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); - } + vips::VOption *option = GetOptionsForImageType(imageType, descriptor); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { image = SetDensity(image, descriptor->density); @@ -473,6 +516,10 @@ namespace sharp { channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB)) .new_from_image(background); } + if (descriptor->createPageHeight > 0) { + image.set(VIPS_META_PAGE_HEIGHT, descriptor->createPageHeight); + image.set(VIPS_META_N_PAGES, static_cast(descriptor->createHeight / descriptor->createPageHeight)); + } image = image.cast(VIPS_FORMAT_UCHAR); imageType = ImageType::RAW; } else if (descriptor->textValue.length() > 0) { @@ -516,31 +563,7 @@ namespace sharp { } if (imageType != ImageType::UNKNOWN) { try { - 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); - } + vips::VOption *option = GetOptionsForImageType(imageType, descriptor); image = VImage::new_from_file(descriptor->file.data(), option); if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { image = SetDensity(image, descriptor->density); @@ -951,14 +974,6 @@ namespace sharp { 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 */ @@ -1001,16 +1016,16 @@ namespace sharp { 0.0722 * colour[2]) }; } - // Add alpha channel to alphaColour colour + // Add alpha channel(s) to alphaColour colour if (colour[3] < 255.0 || image.has_alpha()) { - alphaColour.push_back(colour[3] * multiplier); + int extraBands = image.bands() > 4 ? image.bands() - 3 : 1; + alphaColour.insert(alphaColour.end(), extraBands, colour[3] * multiplier); } // Ensure alphaColour colour uses correct colourspace alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply); // Add non-transparent alpha channel, if required if (colour[3] < 255.0 && !image.has_alpha()) { - image = image.bandjoin( - VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format())); + image = image.bandjoin_const({ 255 * multiplier }); } return std::make_tuple(image, alphaColour); } @@ -1030,9 +1045,7 @@ namespace sharp { */ VImage EnsureAlpha(VImage image, double const value) { if (!image.has_alpha()) { - std::vector alpha; - alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation())); - image = image.bandjoin_const(alpha); + image = image.bandjoin_const({ value * vips_interpretation_max_alpha(image.interpretation()) }); } return image; } diff --git a/src/common.h b/src/common.h index bd942806f..c15755bb0 100644 --- a/src/common.h +++ b/src/common.h @@ -1,13 +1,16 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_COMMON_H_ #define SRC_COMMON_H_ +#include #include #include +#include #include -#include #include #include @@ -15,9 +18,9 @@ // Verify platform and compiler compatibility #if (VIPS_MAJOR_VERSION < 8) || \ - (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 16) || \ - (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 16 && VIPS_MICRO_VERSION < 1) -#error "libvips version 8.16.1+ is required - please see https://sharp.pixelplumbing.com/install" + (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \ + (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 3) +#error "libvips version 8.17.3+ is required - please see https://sharp.pixelplumbing.com/install" #endif #if defined(__has_include) @@ -30,7 +33,7 @@ using vips::VImage; namespace sharp { - struct InputDescriptor { // NOLINT(runtime/indentation_namespace) + struct InputDescriptor { std::string name; std::string file; bool autoOrient; @@ -48,13 +51,13 @@ namespace sharp { int rawWidth; int rawHeight; bool rawPremultiplied; + int rawPageHeight; int pages; int page; - int level; - int subifd; int createChannels; int createWidth; int createHeight; + int createPageHeight; std::vector createBackground; std::string createNoiseType; double createNoiseMean; @@ -77,7 +80,12 @@ namespace sharp { std::vector joinBackground; VipsAlign joinHalign; VipsAlign joinValign; + std::string svgStylesheet; + bool svgHighBitdepth; + int tiffSubifd; + int openSlideLevel; std::vector pdfBackground; + bool jp2Oneshot; InputDescriptor(): autoOrient(false), @@ -95,13 +103,13 @@ namespace sharp { rawWidth(0), rawHeight(0), rawPremultiplied(false), + rawPageHeight(0), pages(1), page(0), - level(0), - subifd(-1), createChannels(0), createWidth(0), createHeight(0), + createPageHeight(0), createBackground{ 0.0, 0.0, 0.0, 255.0 }, createNoiseMean(0.0), createNoiseSigma(0.0), @@ -120,7 +128,11 @@ namespace sharp { joinBackground{ 0.0, 0.0, 0.0, 255.0 }, joinHalign(VIPS_ALIGN_LOW), joinValign(VIPS_ALIGN_LOW), - pdfBackground{ 255.0, 255.0, 255.0, 255.0 } {} + svgHighBitdepth(false), + 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 @@ -160,6 +172,7 @@ namespace sharp { EXR, JXL, RAD, + DCRAW, VIPS, RAW, UNKNOWN, @@ -216,14 +229,9 @@ namespace sharp { ImageType DetermineImageType(char const *file); /* - Does this image type support multiple pages? - */ - bool ImageTypeSupportsPage(ImageType imageType); - - /* - Does this image type support removal of safety limits? + Format-specific options builder */ - bool ImageTypeSupportsUnlimited(ImageType imageType); + vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor); /* Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) @@ -357,12 +365,6 @@ namespace sharp { */ 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 */ diff --git a/src/emscripten/common.gypi b/src/emscripten/common.gypi index 125a52659..714affe59 100644 --- a/src/emscripten/common.gypi +++ b/src/emscripten/common.gypi @@ -19,7 +19,6 @@ '-sAUTO_JS_LIBRARIES=0', '-sAUTO_NATIVE_LIBRARIES=0', '-sDEFAULT_TO_CXX=0', - '-sNODEJS_CATCH_EXIT=0', '-sNODEJS_CATCH_REJECTION=0' ], 'defines': [ diff --git a/src/emscripten/pre.js b/src/emscripten/pre.js index d37a487de..1163b9e77 100644 --- a/src/emscripten/pre.js +++ b/src/emscripten/pre.js @@ -1,5 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ /* global Module, ENV, _vips_shutdown, _uv_library_shutdown */ diff --git a/src/metadata.cc b/src/metadata.cc index 9991d1040..2fde7bf6a 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -1,15 +1,19 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ +#include #include +#include +#include #include -#include #include #include -#include "common.h" -#include "metadata.h" +#include "./common.h" +#include "./metadata.h" static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p); @@ -215,10 +219,10 @@ class MetadataWorker : public Napi::AsyncWorker { if (!baton->levels.empty()) { int i = 0; Napi::Array levels = Napi::Array::New(env, static_cast(baton->levels.size())); - for (std::pair const &l : baton->levels) { + for (const auto& [width, height] : baton->levels) { Napi::Object level = Napi::Object::New(env); - level.Set("width", l.first); - level.Set("height", l.second); + level.Set("width", width); + level.Set("height", height); levels.Set(i++, level); } info.Set("levels", levels); @@ -261,6 +265,10 @@ class MetadataWorker : public Napi::AsyncWorker { info.Set("iptc", Napi::Buffer::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback)); } if (baton->xmpLength > 0) { + if (g_utf8_validate(static_cast(baton->xmp), baton->xmpLength, nullptr)) { + info.Set("xmpAsString", + Napi::String::New(env, static_cast(baton->xmp), baton->xmpLength)); + } info.Set("xmp", Napi::Buffer::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); } if (baton->tifftagPhotoshopLength > 0) { @@ -271,10 +279,10 @@ class MetadataWorker : public Napi::AsyncWorker { if (baton->comments.size() > 0) { int i = 0; Napi::Array comments = Napi::Array::New(env, baton->comments.size()); - for (auto &c : baton->comments) { + for (const auto& [keyword, text] : baton->comments) { Napi::Object comment = Napi::Object::New(env); - comment.Set("keyword", c.first); - comment.Set("text", c.second); + comment.Set("keyword", keyword); + comment.Set("text", text); comments.Set(i++, comment); } info.Set("comments", comments); diff --git a/src/metadata.h b/src/metadata.h index f93dd516e..6f02d452c 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -1,10 +1,13 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_METADATA_H_ #define SRC_METADATA_H_ #include +#include #include #include "./common.h" diff --git a/src/operations.cc b/src/operations.cc index ba3a05138..daeba5ab4 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -1,5 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #include #include @@ -8,8 +10,8 @@ #include #include -#include "common.h" -#include "operations.h" +#include "./common.h" +#include "./operations.h" using vips::VImage; using vips::VError; diff --git a/src/operations.h b/src/operations.h index 22ff46fcf..c281c02cd 100644 --- a/src/operations.h +++ b/src/operations.h @@ -1,5 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_OPERATIONS_H_ #define SRC_OPERATIONS_H_ @@ -8,6 +10,7 @@ #include #include #include +#include #include using vips::VImage; diff --git a/src/pipeline.cc b/src/pipeline.cc index 21529acf1..5f0a3bb0e 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -1,9 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #include #include -#include +#include // NOLINT(build/c++17) #include #include #include @@ -17,9 +19,9 @@ #include #include -#include "common.h" -#include "operations.h" -#include "pipeline.h" +#include "./common.h" +#include "./operations.h" +#include "./pipeline.h" class PipelineWorker : public Napi::AsyncWorker { public: @@ -92,31 +94,22 @@ class PipelineWorker : public Napi::AsyncWorker { // Calculate angle of rotation VipsAngle rotation = VIPS_ANGLE_D0; VipsAngle autoRotation = VIPS_ANGLE_D0; - bool autoFlip = false; bool autoFlop = false; if (baton->input->autoOrient) { // Rotate and flip image according to Exif orientation - std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image)); - image = sharp::RemoveExifOrientation(image); + std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image)); } rotation = CalculateAngleRotation(baton->angle); - // Rotate pre-extract - bool const shouldRotateBefore = baton->rotateBeforePreExtract && - (rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 || - autoFlip || baton->flip || autoFlop || baton->flop || - baton->rotationAngle != 0.0); - - if (shouldRotateBefore) { - image = sharp::StaySequential(image, - rotation != VIPS_ANGLE_D0 || - autoRotation != VIPS_ANGLE_D0 || - autoFlip || - baton->flip || - baton->rotationAngle != 0.0); + bool const shouldRotateBefore = baton->rotateBefore && + (rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0); + bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) && + (autoRotation != VIPS_ANGLE_D0 || autoFlop); + if (shouldOrientBefore) { + image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0); if (autoRotation != VIPS_ANGLE_D0) { if (autoRotation != VIPS_ANGLE_D180) { MultiPageUnsupported(nPages, "Rotate"); @@ -124,14 +117,20 @@ class PipelineWorker : public Napi::AsyncWorker { image = image.rot(autoRotation); autoRotation = VIPS_ANGLE_D0; } - if (autoFlip != baton->flip) { + if (autoFlop) { + image = image.flip(VIPS_DIRECTION_HORIZONTAL); + autoFlop = false; + } + } + + if (shouldRotateBefore) { + image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0); + if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); - autoFlip = false; baton->flip = false; } - if (autoFlop != baton->flop) { + if (baton->flop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); - autoFlop = false; baton->flop = false; } if (rotation != VIPS_ANGLE_D0) { @@ -146,6 +145,7 @@ class PipelineWorker : public Napi::AsyncWorker { std::vector background; std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false); image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory(); + baton->rotationAngle = 0.0; } } @@ -184,8 +184,7 @@ class PipelineWorker : public Napi::AsyncWorker { // When auto-rotating by 90 or 270 degrees, swap the target width and // height to ensure the behavior aligns with how it would have been if // the rotation had taken place *before* resizing. - if (!baton->rotateBeforePreExtract && - (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270)) { + if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) { std::swap(targetResizeWidth, targetResizeHeight); } @@ -207,7 +206,7 @@ class PipelineWorker : public Napi::AsyncWorker { // - input colourspace is not specified; bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) && baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 && - baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !shouldRotateBefore; + baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore); if (shouldPreShrink) { // The common part of the shrink: the bit by which both axes must be shrunk @@ -241,11 +240,7 @@ class PipelineWorker : public Napi::AsyncWorker { // factor for jpegload*, a double scale factor for webpload*, // pdfload* and svgload* if (jpegShrinkOnLoad > 1) { - vips::VOption *option = VImage::option() - ->set("access", access) - ->set("shrink", jpegShrinkOnLoad) - ->set("unlimited", baton->input->unlimited) - ->set("fail_on", baton->input->failOn); + vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad); if (baton->input->buffer != nullptr) { // Reload JPEG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); @@ -256,14 +251,8 @@ class PipelineWorker : public Napi::AsyncWorker { image = VImage::jpegload(const_cast(baton->input->file.data()), option); } } else if (scale != 1.0) { - vips::VOption *option = VImage::option() - ->set("access", access) - ->set("scale", scale) - ->set("fail_on", baton->input->failOn); + vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale); if (inputImageType == sharp::ImageType::WEBP) { - option->set("n", baton->input->pages); - option->set("page", baton->input->page); - if (baton->input->buffer != nullptr) { // Reload WebP buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); @@ -274,9 +263,6 @@ class PipelineWorker : public Napi::AsyncWorker { image = VImage::webpload(const_cast(baton->input->file.data()), option); } } else if (inputImageType == sharp::ImageType::SVG) { - option->set("unlimited", baton->input->unlimited); - option->set("dpi", baton->input->density); - if (baton->input->buffer != nullptr) { // Reload SVG buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); @@ -291,10 +277,6 @@ class PipelineWorker : public Napi::AsyncWorker { throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled"); } } 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) { // Reload PDF buffer VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); @@ -304,7 +286,6 @@ class PipelineWorker : public Napi::AsyncWorker { // Reload PDF file image = VImage::pdfload(const_cast(baton->input->file.data()), option); } - sharp::SetDensity(image, baton->input->density); } } else { @@ -312,6 +293,9 @@ class PipelineWorker : public Napi::AsyncWorker { throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit"); } } + if (baton->input->autoOrient) { + image = sharp::RemoveExifOrientation(image); + } // Any pre-shrinking may already have been done inputWidth = image.width(); @@ -414,7 +398,6 @@ class PipelineWorker : public Napi::AsyncWorker { image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0 || baton->flip || - autoFlip || rotation != VIPS_ANGLE_D0); // Auto-rotate post-extract if (autoRotation != VIPS_ANGLE_D0) { @@ -424,7 +407,7 @@ class PipelineWorker : public Napi::AsyncWorker { image = image.rot(autoRotation); } // Mirror vertically (up-down) about the x-axis - if (baton->flip != autoFlip) { + if (baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); } // Mirror horizontally (left-right) about the y-axis @@ -472,12 +455,10 @@ class PipelineWorker : public Napi::AsyncWorker { std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha); // Embed - int left; - int top; - std::tie(left, top) = sharp::CalculateEmbedPosition( + const auto& [left, top] = sharp::CalculateEmbedPosition( inputWidth, inputHeight, baton->width, baton->height, baton->position); - int width = std::max(inputWidth, baton->width); - int height = std::max(inputHeight, baton->height); + const int width = std::max(inputWidth, baton->width); + const int height = std::max(inputHeight, baton->height); image = nPages > 1 ? sharp::EmbedMultiPage(image, @@ -496,13 +477,10 @@ class PipelineWorker : public Napi::AsyncWorker { // Crop if (baton->position < 9) { // Gravity-based crop - int left; - int top; - - std::tie(left, top) = sharp::CalculateCrop( + const auto& [left, top] = sharp::CalculateCrop( inputWidth, inputHeight, baton->width, baton->height, baton->position); - int width = std::min(inputWidth, baton->width); - int height = std::min(inputHeight, baton->height); + const int width = std::min(inputWidth, baton->width); + const int height = std::min(inputHeight, baton->height); image = nPages > 1 ? sharp::CropMultiPage(image, @@ -531,7 +509,7 @@ class PipelineWorker : public Napi::AsyncWorker { } // Rotate post-extract non-90 angle - if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) { + if (!baton->rotateBefore && baton->rotationAngle != 0.0) { MultiPageUnsupported(nPages, "Rotate"); image = sharp::StaySequential(image); std::vector background; @@ -668,26 +646,20 @@ class PipelineWorker : public Napi::AsyncWorker { sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; composite->input->access = access; std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); - compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline); if (composite->input->autoOrient) { // Respect EXIF Orientation VipsAngle compositeAutoRotation = VIPS_ANGLE_D0; - bool compositeAutoFlip = false; bool compositeAutoFlop = false; - std::tie(compositeAutoRotation, compositeAutoFlip, compositeAutoFlop) = - CalculateExifRotationAndFlip(sharp::ExifOrientation(compositeImage)); + std::tie(compositeAutoRotation, compositeAutoFlop) = + CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage)); compositeImage = sharp::RemoveExifOrientation(compositeImage); - compositeImage = sharp::StaySequential(compositeImage, - compositeAutoRotation != VIPS_ANGLE_D0 || compositeAutoFlip); + compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0); if (compositeAutoRotation != VIPS_ANGLE_D0) { compositeImage = compositeImage.rot(compositeAutoRotation); } - if (compositeAutoFlip) { - compositeImage = compositeImage.flip(VIPS_DIRECTION_VERTICAL); - } if (compositeAutoFlop) { compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL); } @@ -733,8 +705,7 @@ class PipelineWorker : public Napi::AsyncWorker { // gravity was used for extract_area, set it back to its default value of 0 composite->gravity = 0; } - // Ensure image to composite is sRGB with unpremultiplied alpha - compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); + // Ensure image to composite is with unpremultiplied alpha compositeImage = sharp::EnsureAlpha(compositeImage, 1); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply(); // Calculate position @@ -759,7 +730,12 @@ class PipelineWorker : public Napi::AsyncWorker { xs.push_back(left); ys.push_back(top); } - image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys)); + image = VImage::composite(images, modes, VImage::option() + ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST + ? VIPS_INTERPRETATION_sRGB + : baton->colourspacePipeline) + ->set("x", xs) + ->set("y", ys)); image = sharp::RemoveGifPalette(image); } @@ -816,20 +792,14 @@ class PipelineWorker : public Napi::AsyncWorker { image = sharp::EnsureAlpha(image, baton->ensureAlpha); } - // Convert image to sRGB, if not already + // Ensure output colour space if (sharp::Is16Bit(image.interpretation())) { image = image.cast(VIPS_FORMAT_USHORT); } if (image.interpretation() != baton->colourspace) { - // Convert colourspace, pass the current known interpretation so libvips doesn't have to guess image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); - // Transform colours from embedded profile to output profile - if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && - baton->withIccProfile.empty() && sharp::HasProfile(image)) { - image = image.icc_transform(processingProfile, VImage::option() - ->set("embedded", true) - ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) - ->set("intent", VIPS_INTENT_PERCEPTUAL)); + if (inputProfile.first != nullptr && baton->withIccProfile.empty()) { + image = sharp::SetProfile(image, inputProfile); } } @@ -864,8 +834,6 @@ class PipelineWorker : public Napi::AsyncWorker { } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr); } - } else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) { - image = sharp::SetProfile(image, inputProfile); } // Negate the colours in the image @@ -887,11 +855,16 @@ class PipelineWorker : public Napi::AsyncWorker { if (!baton->withExifMerge) { image = sharp::RemoveExif(image); } - for (const auto& s : baton->withExif) { - image.set(s.first.data(), s.second.data()); + for (const auto& [key, value] : baton->withExif) { + image.set(key.c_str(), value.c_str()); } } - + // XMP buffer + if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) { + image = image.copy(); + image.set(VIPS_META_XMP_NAME, nullptr, + const_cast(static_cast(baton->withXmp.c_str())), baton->withXmp.size()); + } // Number of channels used in output image baton->channels = image.bands(); baton->width = image.width(); @@ -1002,6 +975,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("interlace", baton->gifProgressive) ->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) + ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither))); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; @@ -1024,6 +998,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) + ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) @@ -1205,6 +1180,9 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("effort", baton->gifEffort) ->set("reuse", baton->gifReuse) ->set("interlace", baton->gifProgressive) + ->set("interframe_maxerror", baton->gifInterFrameMaxError) + ->set("interpalette_maxerror", baton->gifInterPaletteMaxError) + ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames) ->set("dither", baton->gifDither)); baton->formatOut = "gif"; } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || @@ -1223,6 +1201,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) + ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) @@ -1288,7 +1267,12 @@ class PipelineWorker : public Napi::AsyncWorker { if (what && what[0]) { (baton->err).append(what); } else { - (baton->err).append("Unknown error"); + if (baton->input->failOn == VIPS_FAIL_ON_WARNING) { + (baton->err).append("Warning treated as error due to failOn setting"); + baton->errUseWarning = true; + } else { + (baton->err).append("Unknown error"); + } } } // Clean up libvips' per-request data and threads @@ -1303,7 +1287,11 @@ class PipelineWorker : public Napi::AsyncWorker { // Handle warnings std::string warning = sharp::VipsWarningPop(); while (!warning.empty()) { - debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); + if (baton->errUseWarning) { + (baton->err).append("\n").append(warning); + } else { + debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); + } warning = sharp::VipsWarningPop(); } @@ -1359,7 +1347,8 @@ class PipelineWorker : public Napi::AsyncWorker { // Add file size to info if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) { try { - uint32_t const size = static_cast(std::filesystem::file_size(baton->fileOut)); + uint32_t const size = static_cast( + std::filesystem::file_size(std::filesystem::u8path(baton->fileOut))); info.Set("size", size); } catch (...) {} } @@ -1405,21 +1394,20 @@ class PipelineWorker : public Napi::AsyncWorker { Calculate the angle of rotation and need-to-flip for the given Exif orientation By default, returns zero, i.e. no rotation. */ - std::tuple - CalculateExifRotationAndFlip(int const exifOrientation) { + std::tuple + CalculateExifRotationAndFlop(int const exifOrientation) { VipsAngle rotate = VIPS_ANGLE_D0; - bool flip = false; bool flop = false; switch (exifOrientation) { case 6: rotate = VIPS_ANGLE_D90; break; case 3: rotate = VIPS_ANGLE_D180; break; case 8: rotate = VIPS_ANGLE_D270; break; - case 2: flop = true; break; // flop 1 - case 7: flip = true; rotate = VIPS_ANGLE_D90; break; // flip 6 - case 4: flop = true; rotate = VIPS_ANGLE_D180; break; // flop 3 - case 5: flip = true; rotate = VIPS_ANGLE_D270; break; // flip 8 + case 2: flop = true; break; + case 7: flop = true; rotate = VIPS_ANGLE_D270; break; + case 4: flop = true; rotate = VIPS_ANGLE_D180; break; + case 5: flop = true; rotate = VIPS_ANGLE_D90; break; } - return std::make_tuple(rotate, flip, flop); + return std::make_tuple(rotate, flop); } /* @@ -1447,11 +1435,11 @@ class PipelineWorker : public Napi::AsyncWorker { std::string AssembleSuffixString(std::string extname, std::vector> options) { std::string argument; - for (auto const &option : options) { + for (const auto& [key, value] : options) { if (!argument.empty()) { argument += ","; } - argument += option.first + "=" + option.second; + argument += key + "=" + value; } return extname + "[" + argument + "]"; } @@ -1644,7 +1632,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->angle = sharp::AttrAsInt32(options, "angle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground"); - baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract"); + baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore"); + baton->orientBefore = sharp::AttrAsBool(options, "orientBefore"); baton->flip = sharp::AttrAsBool(options, "flip"); baton->flop = sharp::AttrAsBool(options, "flop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop"); @@ -1716,6 +1705,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { } } baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); + baton->withXmp = sharp::AttrAsStr(options, "withXmp"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->loop = sharp::AttrAsUint32(options, "loop"); baton->delay = sharp::AttrAsInt32Vector(options, "delay"); @@ -1756,9 +1746,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); + baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); + baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); diff --git a/src/pipeline.h b/src/pipeline.h index 66a45f43e..ff9465987 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -1,13 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_PIPELINE_H_ #define SRC_PIPELINE_H_ #include #include -#include #include +#include #include #include @@ -115,7 +117,8 @@ struct PipelineBaton { int angle; double rotationAngle; std::vector rotationBackground; - bool rotateBeforePreExtract; + bool rotateBefore; + bool orientBefore; bool flip; bool flop; int extendTop; @@ -169,10 +172,12 @@ struct PipelineBaton { double gifDither; double gifInterFrameMaxError; double gifInterPaletteMaxError; + bool gifKeepDuplicateFrames; bool gifReuse; bool gifProgressive; int tiffQuality; VipsForeignTiffCompression tiffCompression; + bool tiffBigtiff; VipsForeignTiffPredictor tiffPredictor; bool tiffPyramid; int tiffBitdepth; @@ -195,12 +200,14 @@ struct PipelineBaton { bool jxlLossless; VipsBandFormat rawDepth; std::string err; + bool errUseWarning; int keepMetadata; int withMetadataOrientation; double withMetadataDensity; std::string withIccProfile; std::unordered_map withExif; bool withExifMerge; + std::string withXmp; int timeoutSeconds; std::vector convKernel; int convKernelWidth; @@ -342,10 +349,12 @@ struct PipelineBaton { gifDither(1.0), gifInterFrameMaxError(0.0), gifInterPaletteMaxError(3.0), + gifKeepDuplicateFrames(false), gifReuse(true), gifProgressive(false), tiffQuality(80), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), + tiffBigtiff(false), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPyramid(false), tiffBitdepth(8), @@ -367,6 +376,7 @@ struct PipelineBaton { jxlEffort(7), jxlLossless(false), rawDepth(VIPS_FORMAT_UCHAR), + errUseWarning(false), keepMetadata(0), withMetadataOrientation(-1), withMetadataDensity(0.0), diff --git a/src/sharp.cc b/src/sharp.cc index ed54c501a..7678975c9 100644 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -1,16 +1,18 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -#include // NOLINT(build/c++11) +#include #include #include -#include "common.h" -#include "metadata.h" -#include "pipeline.h" -#include "utilities.h" -#include "stats.h" +#include "./common.h" +#include "./metadata.h" +#include "./pipeline.h" +#include "./stats.h" +#include "./utilities.h" Napi::Object init(Napi::Env env, Napi::Object exports) { static std::once_flag sharp_vips_init_once; diff --git a/src/stats.cc b/src/stats.cc index 61df71a2f..b1fd27a7c 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -1,15 +1,18 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ +#include #include +#include #include -#include #include #include -#include "common.h" -#include "stats.h" +#include "./common.h" +#include "./stats.h" class StatsWorker : public Napi::AsyncWorker { public: @@ -60,7 +63,7 @@ class StatsWorker : public Napi::AsyncWorker { // Image is not opaque when alpha layer is present and contains a non-mamixa value if (image.has_alpha()) { double const minAlpha = static_cast(stats.getpoint(STAT_MIN_INDEX, bands).front()); - if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) { + if (minAlpha != vips_interpretation_max_alpha(image.interpretation())) { baton->isOpaque = false; } } diff --git a/src/stats.h b/src/stats.h index c80e65fa8..88e13c60c 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,10 +1,13 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_STATS_H_ #define SRC_STATS_H_ #include +#include #include #include "./common.h" @@ -24,7 +27,7 @@ struct ChannelStats { ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal, double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal): - min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), + min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), // NOLINT(build/include_what_you_use) mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} }; diff --git a/src/utilities.cc b/src/utilities.cc index 93bd50cdf..4154c08ac 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -1,17 +1,19 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #include -#include #include +#include #include #include #include -#include "common.h" -#include "operations.h" -#include "utilities.h" +#include "./common.h" +#include "./operations.h" +#include "./utilities.h" /* Get and set cache limits @@ -119,7 +121,7 @@ Napi::Value format(const Napi::CallbackInfo& info) { Napi::Object format = Napi::Object::New(env); for (std::string const f : { "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", - "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad" + "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw" }) { // Input const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str()); diff --git a/src/utilities.h b/src/utilities.h index 9272f9558..a1719fa23 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -1,5 +1,7 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ #ifndef SRC_UTILITIES_H_ #define SRC_UTILITIES_H_ diff --git a/test/beforeEach.js b/test/beforeEach.js deleted file mode 100644 index 5a4d4feae..000000000 --- a/test/beforeEach.js +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const sharp = require('../'); - -const usingCache = !process.env.G_DEBUG; -const usingSimd = !process.env.VIPS_NOVECTOR; -const concurrency = Number(process.env.VIPS_CONCURRENCY) || 0; - -exports.mochaHooks = { - beforeEach () { - sharp.cache(usingCache); - sharp.simd(usingSimd); - sharp.concurrency(concurrency); - }, - - afterEach () { - if (global.gc) { - global.gc(); - } - } -}; diff --git a/test/bench/Dockerfile b/test/bench/Dockerfile index 63ad36fd5..290db857a 100644 --- a/test/bench/Dockerfile +++ b/test/bench/Dockerfile @@ -1,11 +1,11 @@ -FROM ubuntu:24.10 +FROM ubuntu:25.04 ARG BRANCH=main # Install basic dependencies RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg # Install latest Node.js LTS -RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh +RUN curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh RUN bash nodesource_setup.sh RUN apt-get install -y nodejs @@ -15,7 +15,7 @@ RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick # Install sharp RUN mkdir /tmp/sharp RUN cd /tmp && git clone --single-branch --branch $BRANCH https://github.com/lovell/sharp.git -RUN cd /tmp/sharp && npm install --build-from-source +RUN cd /tmp/sharp && npm install && npm run build # Install benchmark test RUN cd /tmp/sharp/test/bench && npm install --omit optional diff --git a/test/bench/parallel.js b/test/bench/parallel.js index 5c2568c5b..88d7d4bac 100644 --- a/test/bench/parallel.js +++ b/test/bench/parallel.js @@ -1,11 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ process.env.UV_THREADPOOL_SIZE = 64; -const assert = require('assert'); +const assert = require('node:assert'); const async = require('async'); const sharp = require('../../'); @@ -16,32 +16,29 @@ const height = 480; sharp.concurrency(1); -const timer = setInterval(function () { +const timer = setInterval(() => { console.dir(sharp.counters()); }, 100); -async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function (parallelism, next) { - const start = new Date().getTime(); +async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], (parallelism, next) => { + const start = Date.now(); async.times(parallelism, - function (id, callback) { - /* jslint unused: false */ - sharp(fixtures.inputJpg).resize(width, height).toBuffer(function (err, buffer) { + (_id, callback) => { + sharp(fixtures.inputJpg).resize(width, height).toBuffer((err, buffer) => { buffer = null; - callback(err, new Date().getTime() - start); + callback(err, Date.now() - start); }); }, - function (err, ids) { + (err, ids) => { assert(!err); assert(ids.length === parallelism); ids.sort(); - const mean = ids.reduce(function (a, b) { - return a + b; - }) / ids.length; - console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms'); + const mean = ids.reduce((a, b) => a + b) / ids.length; + console.log(`${parallelism} parallel calls: fastest=${ids[0]}ms slowest=${ids[ids.length - 1]}ms mean=${mean}ms`); next(); } ); -}, function () { +}, () => { clearInterval(timer); console.dir(sharp.counters()); }); diff --git a/test/bench/perf.js b/test/bench/perf.js index 54b05d6b9..375006ef0 100644 --- a/test/bench/perf.js +++ b/test/bench/perf.js @@ -1,10 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const { execSync } = require('child_process'); +const fs = require('node:fs'); +const { execSync } = require('node:child_process'); const async = require('async'); const Benchmark = require('benchmark'); @@ -12,7 +12,7 @@ const Benchmark = require('benchmark'); const safeRequire = (name) => { try { return require(name); - } catch (err) {} + } catch (_err) {} return null; }; @@ -45,13 +45,13 @@ console.log(`Detected ${physicalCores} physical cores`); sharp.concurrency(physicalCores); async.series({ - jpeg: function (callback) { + jpeg: (callback) => { const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const jpegSuite = new Benchmark.Suite('jpeg'); // jimp jpegSuite.add('jimp-buffer-buffer', { defer: true, - fn: async function (deferred) { + fn: async (deferred) => { const image = await Jimp.read(inputJpgBuffer); await image .resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC }) @@ -60,7 +60,7 @@ async.series({ } }).add('jimp-file-file', { defer: true, - fn: async function (deferred) { + fn: async (deferred) => { const image = await Jimp.read(fixtures.inputJpg); await image .resize({ w: width, h: height, mode: Jimp.RESIZE_BICUBIC }) @@ -71,14 +71,14 @@ async.series({ // mapnik mapnik && jpegSuite.add('mapnik-file-file', { defer: true, - fn: function (deferred) { - mapnik.Image.open(fixtures.inputJpg, function (err, img) { + fn: (deferred) => { + mapnik.Image.open(fixtures.inputJpg, (err, img) => { if (err) throw err; img .resize(width, height, { scaling_method: mapnik.imageScaling.lanczos }) - .save(outputJpg, 'jpeg:quality=80', function (err) { + .save(outputJpg, 'jpeg:quality=80', (err) => { if (err) throw err; deferred.resolve(); }); @@ -86,14 +86,14 @@ async.series({ } }).add('mapnik-buffer-buffer', { defer: true, - fn: function (deferred) { - mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) { + fn: (deferred) => { + mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, (err, img) => { if (err) throw err; img .resize(width, height, { scaling_method: mapnik.imageScaling.lanczos }) - .encode('jpeg:quality=80', function (err) { + .encode('jpeg:quality=80', (err) => { if (err) throw err; deferred.resolve(); }); @@ -103,7 +103,7 @@ async.series({ // imagemagick jpegSuite.add('imagemagick-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { imagemagick.resize({ srcPath: fixtures.inputJpg, dstPath: outputJpg, @@ -112,7 +112,7 @@ async.series({ height, format: 'jpg', filter: 'Lanczos' - }, function (err) { + }, (err) => { if (err) { throw err; } else { @@ -124,12 +124,12 @@ async.series({ // gm jpegSuite.add('gm-buffer-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(inputJpgBuffer) .filter('Lanczos') .resize(width, height) .quality(80) - .write(outputJpg, function (err) { + .write(outputJpg, (err) => { if (err) { throw err; } else { @@ -139,12 +139,12 @@ async.series({ } }).add('gm-buffer-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(inputJpgBuffer) .filter('Lanczos') .resize(width, height) .quality(80) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -154,12 +154,12 @@ async.series({ } }).add('gm-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(fixtures.inputJpg) .filter('Lanczos') .resize(width, height) .quality(80) - .write(outputJpg, function (err) { + .write(outputJpg, (err) => { if (err) { throw err; } else { @@ -169,12 +169,12 @@ async.series({ } }).add('gm-file-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(fixtures.inputJpg) .filter('Lanczos') .resize(width, height) .quality(80) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -186,17 +186,17 @@ async.series({ // tfjs tfjs && jpegSuite.add('tfjs-node-buffer-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { const decoded = tfjs.node.decodeJpeg(inputJpgBuffer); const resized = tfjs.image.resizeBilinear(decoded, [height, width]); tfjs .node .encodeJpeg(resized, 'rgb', 80) - .then(function () { + .then(() => { deferred.resolve(); tfjs.disposeVariables(); }) - .catch(function (err) { + .catch((err) => { throw err; }); } @@ -204,10 +204,10 @@ async.series({ // sharp jpegSuite.add('sharp-buffer-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) - .toFile(outputJpg, function (err) { + .toFile(outputJpg, (err) => { if (err) { throw err; } else { @@ -217,10 +217,10 @@ async.series({ } }).add('sharp-buffer-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -230,10 +230,10 @@ async.series({ } }).add('sharp-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputJpg) .resize(width, height) - .toFile(outputJpg, function (err) { + .toFile(outputJpg, (err) => { if (err) { throw err; } else { @@ -243,10 +243,10 @@ async.series({ } }).add('sharp-stream-stream', { defer: true, - fn: function (deferred) { + fn: (deferred) => { const readable = fs.createReadStream(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); - writable.on('finish', function () { + writable.on('finish', () => { deferred.resolve(); }); const pipeline = sharp() @@ -255,10 +255,10 @@ async.series({ } }).add('sharp-file-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputJpg) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -268,34 +268,34 @@ async.series({ } }).add('sharp-promise', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .toBuffer() - .then(function () { + .then(() => { deferred.resolve(); }) - .catch(function (err) { + .catch((err) => { throw err; }); } - }).on('cycle', function (event) { - console.log('jpeg ' + String(event.target)); + }).on('cycle', (event) => { + console.log(`jpeg ${String(event.target)}`); }).on('complete', function () { callback(null, this.filter('fastest').map('name')); }).run(); }, // Effect of applying operations - operations: function (callback) { + operations: (callback) => { const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const operationsSuite = new Benchmark.Suite('operations'); operationsSuite.add('sharp-sharpen-mild', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .sharpen() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -305,11 +305,11 @@ async.series({ } }).add('sharp-sharpen-radius', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .sharpen(3, 1, 3) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -319,11 +319,11 @@ async.series({ } }).add('sharp-blur-mild', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .blur() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -333,11 +333,11 @@ async.series({ } }).add('sharp-blur-radius', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .blur(3) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -347,11 +347,11 @@ async.series({ } }).add('sharp-gamma', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .gamma() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -361,11 +361,11 @@ async.series({ } }).add('sharp-normalise', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .normalise() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -375,11 +375,11 @@ async.series({ } }).add('sharp-greyscale', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .greyscale() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -389,12 +389,12 @@ async.series({ } }).add('sharp-greyscale-gamma', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .gamma() .greyscale() - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -404,11 +404,11 @@ async.series({ } }).add('sharp-progressive', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .jpeg({ progressive: true }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -418,11 +418,11 @@ async.series({ } }).add('sharp-without-chroma-subsampling', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height) .jpeg({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -432,11 +432,11 @@ async.series({ } }).add('sharp-rotate', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .rotate(90) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -446,11 +446,11 @@ async.series({ } }).add('sharp-without-simd', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp.simd(false); sharp(inputJpgBuffer) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { sharp.simd(true); if (err) { throw err; @@ -461,10 +461,10 @@ async.series({ } }).add('sharp-random-access-read', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer, { sequentialRead: false }) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -474,13 +474,13 @@ async.series({ } }).add('sharp-crop-entropy', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height, { fit: 'cover', position: sharp.strategy.entropy }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -490,13 +490,13 @@ async.series({ } }).add('sharp-crop-attention', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height, { fit: 'cover', position: sharp.strategy.attention }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -504,21 +504,21 @@ async.series({ } }); } - }).on('cycle', function (event) { - console.log('operations ' + String(event.target)); + }).on('cycle', (event) => { + console.log(`operations ${String(event.target)}`); }).on('complete', function () { callback(null, this.filter('fastest').map('name')); }).run(); }, // Comparative speed of kernels - kernels: function (callback) { + kernels: (callback) => { const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); (new Benchmark.Suite('kernels')).add('sharp-cubic', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height, { kernel: 'cubic' }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -528,10 +528,10 @@ async.series({ } }).add('sharp-lanczos2', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height, { kernel: 'lanczos2' }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -541,10 +541,36 @@ async.series({ } }).add('sharp-lanczos3', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputJpgBuffer) .resize(width, height, { kernel: 'lanczos3' }) - .toBuffer(function (err) { + .toBuffer((err) => { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('sharp-mks2013', { + defer: true, + fn: (deferred) => { + sharp(inputJpgBuffer) + .resize(width, height, { kernel: 'mks2013' }) + .toBuffer((err) => { + if (err) { + throw err; + } else { + deferred.resolve(); + } + }); + } + }).add('sharp-mks2021', { + defer: true, + fn: (deferred) => { + sharp(inputJpgBuffer) + .resize(width, height, { kernel: 'mks2021' }) + .toBuffer((err) => { if (err) { throw err; } else { @@ -552,21 +578,21 @@ async.series({ } }); } - }).on('cycle', function (event) { - console.log('kernels ' + String(event.target)); + }).on('cycle', (event) => { + console.log(`kernels ${String(event.target)}`); }).on('complete', function () { callback(null, this.filter('fastest').map('name')); }).run(); }, // PNG - png: function (callback) { + png: (callback) => { const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge); const pngSuite = new Benchmark.Suite('png'); const minSamples = 64; // jimp pngSuite.add('jimp-buffer-buffer', { defer: true, - fn: async function (deferred) { + fn: async (deferred) => { const image = await Jimp.read(inputPngBuffer); await image .resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC }) @@ -575,7 +601,7 @@ async.series({ } }).add('jimp-file-file', { defer: true, - fn: async function (deferred) { + fn: async (deferred) => { const image = await Jimp.read(fixtures.inputPngAlphaPremultiplicationLarge); await image .resize({ w: width, h: heightPng, mode: Jimp.RESIZE_BICUBIC }) @@ -586,18 +612,18 @@ async.series({ // mapnik mapnik && pngSuite.add('mapnik-file-file', { defer: true, - fn: function (deferred) { - mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) { + fn: (deferred) => { + mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, (err, img) => { if (err) throw err; - img.premultiply(function (err, img) { + img.premultiply((err, img) => { if (err) throw err; img.resize(width, heightPng, { scaling_method: mapnik.imageScaling.lanczos - }, function (err, img) { + }, (err, img) => { if (err) throw err; - img.demultiply(function (err, img) { + img.demultiply((err, img) => { if (err) throw err; - img.save(outputPng, 'png32:f=no:z=6', function (err) { + img.save(outputPng, 'png32:f=no:z=6', (err) => { if (err) throw err; deferred.resolve(); }); @@ -608,18 +634,18 @@ async.series({ } }).add('mapnik-buffer-buffer', { defer: true, - fn: function (deferred) { - mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, function (err, img) { + fn: (deferred) => { + mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, (err, img) => { if (err) throw err; - img.premultiply(function (err, img) { + img.premultiply((err, img) => { if (err) throw err; img.resize(width, heightPng, { scaling_method: mapnik.imageScaling.lanczos - }, function (err, img) { + }, (err, img) => { if (err) throw err; - img.demultiply(function (err, img) { + img.demultiply((err, img) => { if (err) throw err; - img.encode('png32:f=no:z=6', function (err) { + img.encode('png32:f=no:z=6', (err) => { if (err) throw err; deferred.resolve(); }); @@ -632,7 +658,7 @@ async.series({ // imagemagick pngSuite.add('imagemagick-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { imagemagick.resize({ srcPath: fixtures.inputPngAlphaPremultiplicationLarge, dstPath: outputPng, @@ -643,7 +669,7 @@ async.series({ '-define', 'PNG:compression-level=6', '-define', 'PNG:compression-filter=0' ] - }, function (err) { + }, (err) => { if (err) { throw err; } else { @@ -655,13 +681,13 @@ async.series({ // gm pngSuite.add('gm-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(fixtures.inputPngAlphaPremultiplicationLarge) .filter('Lanczos') .resize(width, heightPng) .define('PNG:compression-level=6') .define('PNG:compression-filter=0') - .write(outputPng, function (err) { + .write(outputPng, (err) => { if (err) { throw err; } else { @@ -671,13 +697,13 @@ async.series({ } }).add('gm-file-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(fixtures.inputPngAlphaPremultiplicationLarge) .filter('Lanczos') .resize(width, heightPng) .define('PNG:compression-level=6') .define('PNG:compression-filter=0') - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -690,11 +716,11 @@ async.series({ pngSuite.add('sharp-buffer-file', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(inputPngBuffer) .resize(width, heightPng) .png({ compressionLevel: 6 }) - .toFile(outputPng, function (err) { + .toFile(outputPng, (err) => { if (err) { throw err; } else { @@ -705,11 +731,11 @@ async.series({ }).add('sharp-buffer-buffer', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(inputPngBuffer) .resize(width, heightPng) .png({ compressionLevel: 6 }) - .toBuffer(function (err, data) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -720,11 +746,11 @@ async.series({ }).add('sharp-file-file', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputPngAlphaPremultiplicationLarge) .resize(width, heightPng) .png({ compressionLevel: 6 }) - .toFile(outputPng, function (err) { + .toFile(outputPng, (err) => { if (err) { throw err; } else { @@ -735,11 +761,11 @@ async.series({ }).add('sharp-file-buffer', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputPngAlphaPremultiplicationLarge) .resize(width, heightPng) .png({ compressionLevel: 6 }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -750,11 +776,11 @@ async.series({ }).add('sharp-progressive', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(inputPngBuffer) .resize(width, heightPng) .png({ compressionLevel: 6, progressive: true }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -765,11 +791,11 @@ async.series({ }).add('sharp-adaptiveFiltering', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(inputPngBuffer) .resize(width, heightPng) .png({ adaptiveFiltering: true, compressionLevel: 6 }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -780,11 +806,11 @@ async.series({ }).add('sharp-compressionLevel=9', { defer: true, minSamples, - fn: function (deferred) { + fn: (deferred) => { sharp(inputPngBuffer) .resize(width, heightPng) .png({ compressionLevel: 9 }) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -793,21 +819,21 @@ async.series({ }); } }); - pngSuite.on('cycle', function (event) { - console.log(' png ' + String(event.target)); + pngSuite.on('cycle', (event) => { + console.log(` png ${String(event.target)}`); }).on('complete', function () { callback(null, this.filter('fastest').map('name')); }).run(); }, // WebP - webp: function (callback) { + webp: (callback) => { const inputWebPBuffer = fs.readFileSync(fixtures.inputWebP); (new Benchmark.Suite('webp')).add('sharp-buffer-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputWebPBuffer) .resize(width, height) - .toFile(outputWebP, function (err) { + .toFile(outputWebP, (err) => { if (err) { throw err; } else { @@ -817,10 +843,10 @@ async.series({ } }).add('sharp-buffer-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(inputWebPBuffer) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -830,10 +856,10 @@ async.series({ } }).add('sharp-file-file', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputWebP) .resize(width, height) - .toFile(outputWebP, function (err) { + .toFile(outputWebP, (err) => { if (err) { throw err; } else { @@ -843,10 +869,10 @@ async.series({ } }).add('sharp-file-buffer', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputWebP) .resize(width, height) - .toBuffer(function (err) { + .toBuffer((err) => { if (err) { throw err; } else { @@ -854,19 +880,19 @@ async.series({ } }); } - }).on('cycle', function (event) { - console.log('webp ' + String(event.target)); + }).on('cycle', (event) => { + console.log(`webp ${String(event.target)}`); }).on('complete', function () { callback(null, this.filter('fastest').map('name')); }).run(); } -}, function (err, results) { +}, (err, results) => { if (err) { throw err; } - Object.keys(results).forEach(function (format) { + Object.keys(results).forEach((format) => { if (results[format].toString().substr(0, 5) !== 'sharp') { - console.log('sharp was slower than ' + results[format] + ' for ' + format); + console.log(`sharp was slower than ${results[format]} for ${format}`); } }); console.dir(sharp.cache()); diff --git a/test/bench/random.js b/test/bench/random.js index 6c6a23786..33a2198da 100644 --- a/test/bench/random.js +++ b/test/bench/random.js @@ -1,11 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const imagemagick = require('imagemagick'); const gm = require('gm'); -const assert = require('assert'); +const assert = require('node:assert'); const Benchmark = require('benchmark'); const sharp = require('../../'); @@ -16,13 +16,11 @@ sharp.cache(false); const min = 320; const max = 960; -const randomDimension = function () { - return Math.ceil((Math.random() * (max - min)) + min); -}; +const randomDimension = () => Math.ceil((Math.random() * (max - min)) + min); new Benchmark.Suite('random').add('imagemagick', { defer: true, - fn: function (deferred) { + fn: (deferred) => { imagemagick.resize({ srcPath: fixtures.inputJpg, dstPath: fixtures.path('output.jpg'), @@ -31,7 +29,7 @@ new Benchmark.Suite('random').add('imagemagick', { height: randomDimension(), format: 'jpg', filter: 'Lanczos' - }, function (err) { + }, (err) => { if (err) { throw err; } else { @@ -41,12 +39,12 @@ new Benchmark.Suite('random').add('imagemagick', { } }).add('gm', { defer: true, - fn: function (deferred) { + fn: (deferred) => { gm(fixtures.inputJpg) .resize(randomDimension(), randomDimension()) .filter('Lanczos') .quality(80) - .toBuffer(function (err, buffer) { + .toBuffer((err, buffer) => { if (err) { throw err; } else { @@ -57,10 +55,10 @@ new Benchmark.Suite('random').add('imagemagick', { } }).add('sharp', { defer: true, - fn: function (deferred) { + fn: (deferred) => { sharp(fixtures.inputJpg) .resize(randomDimension(), randomDimension()) - .toBuffer(function (err, buffer) { + .toBuffer((err, buffer) => { if (err) { throw err; } else { @@ -69,9 +67,9 @@ new Benchmark.Suite('random').add('imagemagick', { } }); } -}).on('cycle', function (event) { +}).on('cycle', (event) => { console.log(String(event.target)); }).on('complete', function () { const winner = this.filter('fastest').map('name'); - assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner); + assert.strictEqual('sharp', String(winner), `sharp was slower than ${winner}`); }).run(); diff --git a/test/fixtures/bonne.geo.tif b/test/fixtures/bonne.geo.tif new file mode 100644 index 000000000..1592bf86c Binary files /dev/null and b/test/fixtures/bonne.geo.tif differ diff --git a/test/fixtures/expected/composite-red-scrgb.png b/test/fixtures/expected/composite-red-scrgb.png new file mode 100644 index 000000000..fd2bdf940 Binary files /dev/null and b/test/fixtures/expected/composite-red-scrgb.png differ diff --git a/test/fixtures/expected/extract.tiff b/test/fixtures/expected/extract.tiff index 74c2cac3f..e02db48b2 100644 Binary files a/test/fixtures/expected/extract.tiff and b/test/fixtures/expected/extract.tiff differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index f5b686ad7..2448843e2 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -1,16 +1,14 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const path = require('path'); +const path = require('node:path'); const sharp = require('../../'); const maxColourDistance = require('../../lib/sharp')._maxColourDistance; // Helpers -const getPath = function (filename) { - return path.join(__dirname, filename); -}; +const getPath = (filename) => path.join(__dirname, filename); // Generates a 64-bit-as-binary-string image fingerprint // Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html @@ -22,7 +20,7 @@ async function fingerprint (image) { .resize(9, 8, { fit: sharp.fit.fill }) .raw() .toBuffer() - .then(function (data) { + .then((data) => { let fingerprint = ''; for (let col = 0; col < 8; col++) { for (let row = 0; row < 8; row++) { @@ -115,8 +113,10 @@ module.exports = { inputTiff8BitDepth: getPath('8bit_depth.tiff'), inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600 inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045 + inputTiffGeo: getPath('bonne.geo.tif'), // https://download.osgeo.org/geotiff/samples/intergraph inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2 + inputJp2TileParts: getPath('relax_tileparts.jp2'), // kdu_expand -i relax.jp2 -o relax-tmp.tif ; kdu_compress -i relax-tmp.tif -o relax_tileparts.jp2 -jp2_space sRGB Clayers=8 -rate 1.0,0.04 Stiles='{128,128}' ORGtparts=L ; rm relax-tmp.tif 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 inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif @@ -146,14 +146,12 @@ module.exports = { path: getPath, // Path for expected output images - expected: function (filename) { - return getPath(path.join('expected', filename)); - }, + expected: (filename) => getPath(path.join('expected', filename)), // Verify similarity of expected vs actual images via fingerprint // Specify distance threshold using `options={threshold: 42}`, default // `threshold` is 5; - assertSimilar: async function (expectedImage, actualImage, options, callback) { + assertSimilar: async (expectedImage, actualImage, options, callback) => { if (typeof options === 'function') { callback = options; options = {}; @@ -193,12 +191,12 @@ module.exports = { } }, - assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) { + assertMaxColourDistance: (actualImagePath, expectedImagePath, acceptedDistance) => { if (typeof actualImagePath !== 'string') { - throw new TypeError('`actualImagePath` must be a string; got ' + actualImagePath); + throw new TypeError(`\`actualImagePath\` must be a string; got ${actualImagePath}`); } if (typeof expectedImagePath !== 'string') { - throw new TypeError('`expectedImagePath` must be a string; got ' + expectedImagePath); + throw new TypeError(`\`expectedImagePath\` must be a string; got ${expectedImagePath}`); } if (typeof acceptedDistance !== 'number') { // Default threshold @@ -206,7 +204,7 @@ module.exports = { } const distance = maxColourDistance(actualImagePath, expectedImagePath); if (distance > acceptedDistance) { - throw new Error('Expected maximum absolute distance of ' + acceptedDistance + ', actual ' + distance); + throw new Error(`Expected maximum absolute distance of ${acceptedDistance}, actual ${distance}`); } } diff --git a/test/fixtures/relax_tileparts.jp2 b/test/fixtures/relax_tileparts.jp2 new file mode 100644 index 000000000..62621c689 Binary files /dev/null and b/test/fixtures/relax_tileparts.jp2 differ diff --git a/test/leak/leak.sh b/test/leak/leak.sh index c707a6a43..c1e5a0a33 100755 --- a/test/leak/leak.sh +++ b/test/leak/leak.sh @@ -18,5 +18,5 @@ for test in $TESTS; do --show-leak-kinds=definite,indirect \ --num-callers=20 \ --trace-children=yes \ - node --expose-gc --zero-fill-buffers node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "test/unit/$test"; + node --zero-fill-buffers --test "test/unit/$test"; done diff --git a/test/types/sharp.test-d.ts b/test/types/sharp.test-d.ts index d968e07c3..9364c08c8 100644 --- a/test/types/sharp.test-d.ts +++ b/test/types/sharp.test-d.ts @@ -1,6 +1,9 @@ +// biome-ignore-all lint/correctness/noUnusedFunctionParameters: types only test file +// biome-ignore-all lint/correctness/noUnusedVariables: types only test file + import sharp = require('../../'); -import { createReadStream, createWriteStream } from 'fs'; +import { createReadStream, createWriteStream } from 'node:fs'; const input: Buffer = Buffer.alloc(0); const readableStream: NodeJS.ReadableStream = createReadStream(input); @@ -79,7 +82,7 @@ sharp({ let transformer = sharp() .resize(300) .on('info', (info: sharp.OutputInfo) => { - console.log('Image height is ' + info.height); + console.log(`Image height is ${info.height}`); }); readableStream.pipe(transformer).pipe(writableStream); @@ -188,6 +191,8 @@ sharp(input) // of the image data in inputBuffer }); +sharp(input).resize({ kernel: 'mks2013' }); + transformer = sharp() .resize(200, 200, { fit: 'cover', @@ -316,7 +321,7 @@ sharp('input.gif') // From https://sharp.pixelplumbing.com/api-output#examples-9 // Extract raw RGB pixel data from JPEG input sharp('input.jpg') - .raw() + .raw({ depth: 'ushort' }) .toBuffer({ resolveWithObject: true }) .then(({ data, info }) => { console.log(data); @@ -373,6 +378,8 @@ sharp(input) .gif({ reuse: false }) .gif({ progressive: true }) .gif({ progressive: false }) + .gif({ keepDuplicateFrames: true }) + .gif({ keepDuplicateFrames: false }) .toBuffer({ resolveWithObject: true }) .then(({ data, info }) => { console.log(data); @@ -414,6 +421,7 @@ sharp({ channels: 4, height: 25000, width: 25000, + pageHeight: 1000, }, limitInputPixels: false, }) @@ -431,9 +439,6 @@ sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile // Support `unlimited` input option 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 sharp({ create: { @@ -690,6 +695,8 @@ sharp(input) k2: 'v2' } }) + .keepXmp() + .withXmp('test') .keepIccProfile() .withIccProfile('filename') .withIccProfile('filename', { attach: false }); @@ -716,10 +723,29 @@ sharp(input).composite([ } ]) +// Support format-specific input options const colour: sharp.Colour = '#fff'; const color: sharp.Color = '#fff'; -sharp({ pdfBackground: colour }); -sharp({ pdfBackground: color }); +sharp({ pdf: { background: colour } }); +sharp({ pdf: { background: 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: false }); diff --git a/test/unit.mjs b/test/unit.mjs new file mode 100644 index 000000000..0a1aa3c0f --- /dev/null +++ b/test/unit.mjs @@ -0,0 +1,16 @@ +import { readdir } from 'node:fs/promises'; +import { run } from 'node:test'; +import { spec } from 'node:test/reporters'; + +const files = (await readdir('./test/unit')).map((f) => `./test/unit/${f}`); + +run({ + files, + concurrency: true, + timeout: 60000, + coverage: true, + coverageIncludeGlobs: ['lib/*.js'], + branchCoverage: 100, +}) + .compose(new spec()) + .pipe(process.stdout); diff --git a/test/unit/affine.js b/test/unit/affine.js index 2122f0072..43ff8ed92 100644 --- a/test/unit/affine.js +++ b/test/unit/affine.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); diff --git a/test/unit/alpha.js b/test/unit/alpha.js index c7c0d6e6c..42e178098 100644 --- a/test/unit/alpha.js +++ b/test/unit/alpha.js @@ -1,18 +1,19 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); const sharp = require('../../'); -describe('Alpha transparency', function () { - it('Flatten to black', function (done) { +describe('Alpha transparency', () => { + it('Flatten to black', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .flatten() .resize(400, 300) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(400, info.width); assert.strictEqual(300, info.height); @@ -20,14 +21,14 @@ describe('Alpha transparency', function () { }); }); - it('Flatten to RGB orange', function (done) { + it('Flatten to RGB orange', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(400, 300) .flatten({ background: { r: 255, g: 102, b: 0 } }) .jpeg({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(400, info.width); assert.strictEqual(300, info.height); @@ -35,12 +36,12 @@ describe('Alpha transparency', function () { }); }); - it('Flatten to CSS/hex orange', function (done) { + it('Flatten to CSS/hex orange', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(400, 300) .flatten({ background: '#ff6600' }) .jpeg({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(400, info.width); assert.strictEqual(300, info.height); @@ -48,13 +49,13 @@ describe('Alpha transparency', function () { }); }); - it('Flatten 16-bit PNG with transparency to orange', function (done) { + it('Flatten 16-bit PNG with transparency to orange', (_t, done) => { const output = fixtures.path('output.flatten-rgb16-orange.jpg'); sharp(fixtures.inputPngWithTransparency16bit) .flatten({ background: { r: 255, g: 102, b: 0 } }) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual(32, info.width); @@ -64,10 +65,10 @@ describe('Alpha transparency', function () { }); }); - it('Do not flatten', function (done) { + it('Do not flatten', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .flatten(false) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); @@ -75,10 +76,10 @@ describe('Alpha transparency', function () { }); }); - it('Ignored for JPEG', function (done) { + it('Ignored for JPEG', (_t, done) => { sharp(fixtures.inputJpg) .flatten({ background: '#ff0000' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(3, info.channels); @@ -98,68 +99,60 @@ describe('Alpha transparency', function () { }); }); - it('Enlargement with non-nearest neighbor interpolation shouldn’t cause dark edges', function () { + it('Enlargement with non-nearest neighbor interpolation shouldn’t cause dark edges', () => { const base = 'alpha-premultiply-enlargement-2048x1536-paper.png'; - const actual = fixtures.path('output.' + base); + const actual = fixtures.path(`output.${base}`); const expected = fixtures.expected(base); return sharp(fixtures.inputPngAlphaPremultiplicationSmall) .resize(2048, 1536) .toFile(actual) - .then(function () { + .then(() => { fixtures.assertMaxColourDistance(actual, expected, 102); }); }); - it('Reduction with non-nearest neighbor interpolation shouldn’t cause dark edges', function () { + it('Reduction with non-nearest neighbor interpolation shouldn’t cause dark edges', () => { const base = 'alpha-premultiply-reduction-1024x768-paper.png'; - const actual = fixtures.path('output.' + base); + const actual = fixtures.path(`output.${base}`); const expected = fixtures.expected(base); return sharp(fixtures.inputPngAlphaPremultiplicationLarge) .resize(1024, 768) .toFile(actual) - .then(function () { + .then(() => { fixtures.assertMaxColourDistance(actual, expected, 102); }); }); - it('Removes alpha from fixtures with transparency, ignores those without', function () { - return Promise.all([ + it('Removes alpha from fixtures with transparency, ignores those without', () => Promise.all([ fixtures.inputPngWithTransparency, fixtures.inputPngWithTransparency16bit, fixtures.inputWebPWithTransparency, fixtures.inputJpg, fixtures.inputPng, fixtures.inputWebP - ].map(function (input) { - return sharp(input) + ].map((input) => sharp(input) .resize(10) .removeAlpha() .toBuffer({ resolveWithObject: true }) - .then(function (result) { + .then((result) => { assert.strictEqual(3, result.info.channels); - }); - })); - }); + })))); - it('Ensures alpha from fixtures without transparency, ignores those with', function () { - return Promise.all([ + it('Ensures alpha from fixtures without transparency, ignores those with', () => Promise.all([ fixtures.inputPngWithTransparency, fixtures.inputPngWithTransparency16bit, fixtures.inputWebPWithTransparency, fixtures.inputJpg, fixtures.inputPng, fixtures.inputWebP - ].map(function (input) { - return sharp(input) + ].map((input) => sharp(input) .resize(10) .ensureAlpha() .png() .toBuffer({ resolveWithObject: true }) - .then(function (result) { + .then((result) => { assert.strictEqual(4, result.info.channels); - }); - })); - }); + })))); it('Valid ensureAlpha value used for alpha channel', async () => { const background = { r: 255, g: 0, b: 0 }; diff --git a/test/unit/avif.js b/test/unit/avif.js index 9eeb50000..6fb08c6fb 100644 --- a/test/unit/avif.js +++ b/test/unit/avif.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures'); @@ -20,8 +21,8 @@ describe('AVIF', () => { .resize(32) .jpeg() .toBuffer(); - const { size, ...metadata } = await sharp(data) - .metadata(); + const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 13, @@ -49,8 +50,8 @@ describe('AVIF', () => { .resize(32) .avif({ effort: 0 }) .toBuffer(); - const { size, ...metadata } = await sharp(data) - .metadata(); + const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 26, @@ -77,8 +78,8 @@ describe('AVIF', () => { const data = await sharp(inputAvif) .resize(32) .toBuffer(); - const { size, ...metadata } = await sharp(data) - .metadata(); + const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 13, @@ -106,8 +107,8 @@ describe('AVIF', () => { .resize(10) .avif({ effort: 0 }) .toBuffer(); - const { size, ...metadata } = await sharp(data) - .metadata(); + const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 300, @@ -136,8 +137,8 @@ describe('AVIF', () => { .sharpen() .avif({ effort: 0 }) .toBuffer(); - const { size, ...metadata } = await sharp(data) - .metadata(); + const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 26, @@ -174,9 +175,10 @@ describe('AVIF', () => { ) ); - it('Invalid bitdepth value throws error', async () => { - assert.rejects( + it('Invalid bitdepth value throws error', () => + assert.throws( () => sharp().avif({ bitdepth: 11 }), - /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/); - }); + /Expected 8, 10 or 12 for bitdepth but received 11 of type number/ + ) + ); }); diff --git a/test/unit/bandbool.js b/test/unit/bandbool.js index 66ad404cb..073fb68ba 100644 --- a/test/unit/bandbool.js +++ b/test/unit/bandbool.js @@ -1,51 +1,52 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); const sharp = require('../../'); -describe('Bandbool per-channel boolean operations', function () { +describe('Bandbool per-channel boolean operations', () => { [ sharp.bool.and, sharp.bool.or, sharp.bool.eor ] - .forEach(function (op) { - it(op + ' operation', function (done) { + .forEach((op) => { + it(`${op} operation`, (_t, done) => { sharp(fixtures.inputPngBooleanNoAlpha) .bandbool(op) .toColourspace('b-w') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(200, info.width); assert.strictEqual(200, info.height); assert.strictEqual(1, info.channels); - fixtures.assertSimilar(fixtures.expected('bandbool_' + op + '_result.png'), data, done); + fixtures.assertSimilar(fixtures.expected(`bandbool_${op}_result.png`), data, done); }); }); }); - it('sRGB image retains 3 channels', function (done) { + it('sRGB image retains 3 channels', (_t, done) => { sharp(fixtures.inputJpg) .bandbool('and') - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(3, info.channels); done(); }); }); - it('Invalid operation', function () { - assert.throws(function () { + it('Invalid operation', () => { + assert.throws(() => { sharp().bandbool('fail'); }); }); - it('Missing operation', function () { - assert.throws(function () { + it('Missing operation', () => { + assert.throws(() => { sharp().bandbool(); }); }); diff --git a/test/unit/blur.js b/test/unit/blur.js index facff648b..f5132098b 100644 --- a/test/unit/blur.js +++ b/test/unit/blur.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Blur', function () { - it('specific radius 1', function (done) { +describe('Blur', () => { + it('specific radius 1', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur(1) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -22,11 +23,11 @@ describe('Blur', function () { }); }); - it('specific radius 10', function (done) { + it('specific radius 10', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur(10) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -35,11 +36,11 @@ describe('Blur', function () { }); }); - it('specific options.sigma 10', function (done) { + it('specific options.sigma 10', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur({ sigma: 10 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -48,11 +49,11 @@ describe('Blur', function () { }); }); - it('specific radius 0.3', function (done) { + it('specific radius 0.3', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur(0.3) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -61,11 +62,11 @@ describe('Blur', function () { }); }); - it('mild blur', function (done) { + it('mild blur', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -74,17 +75,17 @@ describe('Blur', function () { }); }); - it('invalid radius', function () { - assert.throws(function () { + it('invalid radius', () => { + assert.throws(() => { sharp(fixtures.inputJpg).blur(0.1); }); }); - it('blurred image is smaller than non-blurred', function (done) { + it('blurred image is smaller than non-blurred', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .blur(false) - .toBuffer(function (err, notBlurred, info) { + .toBuffer((err, notBlurred, info) => { if (err) throw err; assert.strictEqual(true, notBlurred.length > 0); assert.strictEqual('jpeg', info.format); @@ -93,7 +94,7 @@ describe('Blur', function () { sharp(fixtures.inputJpg) .resize(320, 240) .blur(true) - .toBuffer(function (err, blurred, info) { + .toBuffer((err, blurred, info) => { if (err) throw err; assert.strictEqual(true, blurred.length > 0); assert.strictEqual(true, blurred.length < notBlurred.length); @@ -105,18 +106,18 @@ describe('Blur', function () { }); }); - it('invalid precision', function () { - assert.throws(function () { + it('invalid precision', () => { + assert.throws(() => { sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' }); }, /Expected one of: integer, float, approximate for precision but received invalid of type string/); }); - it('invalid minAmplitude', function () { - assert.throws(function () { + it('invalid minAmplitude', () => { + assert.throws(() => { sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 }); }, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 1.01 }); }, /Expected number between 0.001 and 1 for minAmplitude but received 1.01 of type number/); }); @@ -149,8 +150,8 @@ describe('Blur', function () { await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow); }); - it('options.sigma is required if options object is passed', function () { - assert.throws(function () { + it('options.sigma is required if options object is passed', () => { + assert.throws(() => { sharp(fixtures.inputJpg).blur({ precision: 'invalid' }); }, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/); }); diff --git a/test/unit/boolean.js b/test/unit/boolean.js index c5fcaf67d..f85b376da 100644 --- a/test/unit/boolean.js +++ b/test/unit/boolean.js @@ -1,15 +1,16 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); const sharp = require('../../'); -describe('Boolean operation between two images', function () { +describe('Boolean operation between two images', () => { const inputJpgBooleanTestBuffer = fs.readFileSync(fixtures.inputJpgBooleanTest); [ @@ -17,63 +18,63 @@ describe('Boolean operation between two images', function () { sharp.bool.or, sharp.bool.eor ] - .forEach(function (op) { - it(op + ' operation, file', function (done) { + .forEach((op) => { + it(`${op} operation, file`, (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .boolean(fixtures.inputJpgBooleanTest, op) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - fixtures.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); + fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done); }); }); - it(op + ' operation, buffer', function (done) { + it(`${op} operation, buffer`, (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .boolean(inputJpgBooleanTestBuffer, op) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - fixtures.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); + fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done); }); }); - it(op + ' operation, raw', function (done) { + it(`${op} operation, raw`, (_t, done) => { sharp(fixtures.inputJpgBooleanTest) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; sharp(fixtures.inputJpg) .resize(320, 240) .boolean(data, op, { raw: info }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - fixtures.assertSimilar(fixtures.expected('boolean_' + op + '_result.jpg'), data, done); + fixtures.assertSimilar(fixtures.expected(`boolean_${op}_result.jpg`), data, done); }); }); }); }); - it('Invalid operation', function () { - assert.throws(function () { + it('Invalid operation', () => { + assert.throws(() => { sharp().boolean(fixtures.inputJpgBooleanTest, 'fail'); }); }); - it('Invalid operation, non-string', function () { - assert.throws(function () { + it('Invalid operation, non-string', () => { + assert.throws(() => { sharp().boolean(fixtures.inputJpgBooleanTest, null); }); }); - it('Missing input', function () { - assert.throws(function () { + it('Missing input', () => { + assert.throws(() => { sharp().boolean(); }); }); diff --git a/test/unit/clahe.js b/test/unit/clahe.js index 9c93536bf..f3d68ebbb 100644 --- a/test/unit/clahe.js +++ b/test/unit/clahe.js @@ -1,139 +1,140 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../lib'); const fixtures = require('../fixtures'); -describe('Clahe', function () { - it('width 5 width 5 maxSlope 0', function (done) { +describe('Clahe', () => { + it('width 5 width 5 maxSlope 0', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 5, height: 5, maxSlope: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-5-5-0.jpg'), data, { threshold: 10 }, done); }); }); - it('width 5 width 5 maxSlope 5', function (done) { + it('width 5 width 5 maxSlope 5', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 5, height: 5, maxSlope: 5 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-5-5-5.jpg'), data, done); }); }); - it('width 11 width 25 maxSlope 14', function (done) { + it('width 11 width 25 maxSlope 14', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 11, height: 25, maxSlope: 14 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-11-25-14.jpg'), data, done); }); }); - it('width 50 width 50 maxSlope 0', function (done) { + it('width 50 width 50 maxSlope 0', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 50, height: 50, maxSlope: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-50-50-0.jpg'), data, done); }); }); - it('width 50 width 50 maxSlope 14', function (done) { + it('width 50 width 50 maxSlope 14', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 50, height: 50, maxSlope: 14 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-50-50-14.jpg'), data, done); }); }); - it('width 100 width 50 maxSlope 3', function (done) { + it('width 100 width 50 maxSlope 3', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 100, height: 50, maxSlope: 3 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done); }); }); - it('width 100 width 100 maxSlope 0', function (done) { + it('width 100 width 100 maxSlope 0', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 100, height: 100, maxSlope: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-100-100-0.jpg'), data, done); }); }); - it('invalid maxSlope', function () { - assert.throws(function () { + it('invalid maxSlope', () => { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: -5 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 110 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 5.5 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100, maxSlope: 'a string' }); }); }); - it('invalid width', function () { - assert.throws(function () { + it('invalid width', () => { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100.5, height: 100 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: -5, height: 100 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: true, height: 100 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 'string test', height: 100 }); }); }); - it('invalid height', function () { - assert.throws(function () { + it('invalid height', () => { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 100.5 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: -5 }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: true }); }); - assert.throws(function () { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe({ width: 100, height: 'string test' }); }); }); - it('invalid options object', function () { - assert.throws(function () { + it('invalid options object', () => { + assert.throws(() => { sharp(fixtures.inputJpgClahe).clahe(100, 100, 5); }); }); - it('uses default maxSlope of 3', function (done) { + it('uses default maxSlope of 3', (_t, done) => { sharp(fixtures.inputJpgClahe) .clahe({ width: 100, height: 50 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fixtures.assertSimilar(fixtures.expected('clahe-100-50-3.jpg'), data, done); diff --git a/test/unit/clone.js b/test/unit/clone.js index 0db81ae53..dae53b20b 100644 --- a/test/unit/clone.js +++ b/test/unit/clone.js @@ -1,29 +1,30 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { afterEach, beforeEach, describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Clone', function () { - beforeEach(function () { +describe('Clone', () => { + beforeEach(() => { sharp.cache(false); }); - afterEach(function () { + afterEach(() => { sharp.cache(true); }); - it('Read from Stream and write to multiple Streams', function (done) { + it('Read from Stream and write to multiple Streams', (_t, done) => { let finishEventsExpected = 2; // Output stream 1 const output1 = fixtures.path('output.multi-stream.1.jpg'); const writable1 = fs.createWriteStream(output1); - writable1.on('finish', function () { - sharp(output1).toBuffer(function (err, data, info) { + writable1.on('finish', () => { + sharp(output1).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -40,8 +41,8 @@ describe('Clone', function () { // Output stream 2 const output2 = fixtures.path('output.multi-stream.2.jpg'); const writable2 = fs.createWriteStream(output2); - writable2.on('finish', function () { - sharp(output2).toBuffer(function (err, data, info) { + writable2.on('finish', () => { + sharp(output2).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -64,14 +65,14 @@ describe('Clone', function () { fs.createReadStream(fixtures.inputJpg).pipe(rotator); }); - it('Stream-based input attaches finish event listener to original', function () { + it('Stream-based input attaches finish event listener to original', () => { const original = sharp(); const clone = original.clone(); assert.strictEqual(1, original.listenerCount('finish')); assert.strictEqual(0, clone.listenerCount('finish')); }); - it('Non Stream-based input does not attach finish event listeners', function () { + it('Non Stream-based input does not attach finish event listeners', () => { const original = sharp(fixtures.inputJpg); const clone = original.clone(); assert.strictEqual(0, original.listenerCount('finish')); diff --git a/test/unit/colourspace.js b/test/unit/colourspace.js index 33e4e329b..9c908051c 100644 --- a/test/unit/colourspace.js +++ b/test/unit/colourspace.js @@ -1,22 +1,23 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Colour space conversion', function () { - it('To greyscale', function (done) { +describe('Colour space conversion', () => { + it('To greyscale', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .greyscale() .toFile(fixtures.path('output.greyscale-gamma-0.0.jpg'), done); }); - it('To greyscale with gamma correction', function (done) { + it('To greyscale with gamma correction', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .gamma() @@ -24,19 +25,19 @@ describe('Colour space conversion', function () { .toFile(fixtures.path('output.greyscale-gamma-2.2.jpg'), done); }); - it('Not to greyscale', function (done) { + it('Not to greyscale', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .greyscale(false) .toFile(fixtures.path('output.greyscale-not.jpg'), done); }); - it('Greyscale with single channel output', function (done) { + it('Greyscale with single channel output', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .greyscale() .toColourspace('b-w') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(1, info.channels); assert.strictEqual(320, info.width); @@ -55,10 +56,10 @@ describe('Colour space conversion', function () { assert.strictEqual(format, 'webp'); }); - it('From CMYK to sRGB', function (done) { + it('From CMYK to sRGB', (_t, done) => { sharp(fixtures.inputJpgWithCmykProfile) .resize(320) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -67,13 +68,13 @@ describe('Colour space conversion', function () { }); }); - it('From CMYK to sRGB with white background, not yellow', function (done) { + it('From CMYK to sRGB with white background, not yellow', (_t, done) => { sharp(fixtures.inputJpgWithCmykProfile) .resize(320, 240, { fit: sharp.fit.contain, background: 'white' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -82,10 +83,10 @@ describe('Colour space conversion', function () { }); }); - it('From profile-less CMYK to sRGB', function (done) { + it('From profile-less CMYK to sRGB', (_t, done) => { sharp(fixtures.inputJpgWithCmykNoProfile) .resize(320) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -122,14 +123,14 @@ describe('Colour space conversion', function () { ); }); - it('CMYK profile to CMYK profile with negate', (done) => { + it('CMYK profile to CMYK profile with negate', (_t, done) => { sharp(fixtures.inputTiffFogra) .resize(320, 240) .toColourspace('cmyk') .pipelineColourspace('cmyk') .withIccProfile(fixtures.path('XCMYK 2017.icc')) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('tiff', info.format); assert.strictEqual(320, info.width); @@ -143,13 +144,13 @@ describe('Colour space conversion', function () { }); }); - it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', function (done) { + it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', (_t, done) => { sharp(fixtures.inputPngGradients) .pipelineColourspace('rgb16') .resize(320) .gamma() .toColourspace('srgb') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); fixtures.assertSimilar(fixtures.expected('colourspace-gradients-gamma-resize.png'), data, { @@ -177,15 +178,15 @@ describe('Colour space conversion', function () { assert.strictEqual(b, 34); }); - it('Invalid pipelineColourspace input', function () { - assert.throws(function () { + it('Invalid pipelineColourspace input', () => { + assert.throws(() => { sharp(fixtures.inputJpg) .pipelineColorspace(null); }, /Expected string for colourspace but received null of type object/); }); - it('Invalid toColourspace input', function () { - assert.throws(function () { + it('Invalid toColourspace input', () => { + assert.throws(() => { sharp(fixtures.inputJpg) .toColourspace(null); }); diff --git a/test/unit/composite.js b/test/unit/composite.js index 865ac5981..0df0007aa 100644 --- a/test/unit/composite.js +++ b/test/unit/composite.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); const sharp = require('../../'); @@ -122,6 +123,26 @@ 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 () => { const filename = 'composite-multiple.png'; const actual = fixtures.path(`output.${filename}`); @@ -302,7 +323,7 @@ describe('composite', () => { describe('string gravity', () => { Object.keys(sharp.gravity).forEach(gravity => { it(gravity, done => { - const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg'); + const expected = fixtures.expected(`overlay-gravity-${gravity}.jpg`); sharp(fixtures.inputJpg) .resize(80) .composite([{ @@ -324,7 +345,7 @@ describe('composite', () => { describe('tile and gravity', () => { Object.keys(sharp.gravity).forEach(gravity => { it(gravity, done => { - const expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg'); + const expected = fixtures.expected(`overlay-tile-gravity-${gravity}.jpg`); sharp(fixtures.inputJpg) .resize(80) .composite([{ diff --git a/test/unit/convolve.js b/test/unit/convolve.js index dfb8dd52c..bc430178a 100644 --- a/test/unit/convolve.js +++ b/test/unit/convolve.js @@ -1,15 +1,16 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Convolve', function () { - it('specific convolution kernel 1', function (done) { +describe('Convolve', () => { + it('specific convolution kernel 1', (_t, done) => { sharp(fixtures.inputPngStripesV) .convolve({ width: 3, @@ -22,7 +23,7 @@ describe('Convolve', function () { 10, 20, 10 ] }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -31,7 +32,7 @@ describe('Convolve', function () { }); }); - it('specific convolution kernel 2', function (done) { + it('specific convolution kernel 2', (_t, done) => { sharp(fixtures.inputPngStripesH) .convolve({ width: 3, @@ -42,7 +43,7 @@ describe('Convolve', function () { 1, 0, 1 ] }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -51,7 +52,7 @@ describe('Convolve', function () { }); }); - it('horizontal Sobel operator', function (done) { + it('horizontal Sobel operator', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .convolve({ @@ -63,7 +64,7 @@ describe('Convolve', function () { -1, 0, 1 ] }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -72,14 +73,14 @@ describe('Convolve', function () { }); }); - describe('invalid kernel specification', function () { - it('missing', function () { - assert.throws(function () { + describe('invalid kernel specification', () => { + it('missing', () => { + assert.throws(() => { sharp(fixtures.inputJpg).convolve({}); }); }); - it('incorrect data format', function () { - assert.throws(function () { + it('incorrect data format', () => { + assert.throws(() => { sharp(fixtures.inputJpg).convolve({ width: 3, height: 3, @@ -87,8 +88,8 @@ describe('Convolve', function () { }); }); }); - it('incorrect dimensions', function () { - assert.throws(function () { + it('incorrect dimensions', () => { + assert.throws(() => { sharp(fixtures.inputJpg).convolve({ width: 3, height: 4, diff --git a/test/unit/dilate.js b/test/unit/dilate.js index 94588a285..f1f982cb6 100644 --- a/test/unit/dilate.js +++ b/test/unit/dilate.js @@ -1,15 +1,14 @@ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Dilate', function () { - it('dilate 1 png', function (done) { +describe('Dilate', () => { + it('dilate 1 png', (_t, done) => { sharp(fixtures.inputPngDotAndLines) .dilate(1) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(100, info.width); @@ -18,10 +17,10 @@ describe('Dilate', function () { }); }); - it('dilate 1 png - default width', function (done) { + it('dilate 1 png - default width', (_t, done) => { sharp(fixtures.inputPngDotAndLines) .dilate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(100, info.width); @@ -30,8 +29,8 @@ describe('Dilate', function () { }); }); - it('invalid dilation width', function () { - assert.throws(function () { + it('invalid dilation width', () => { + assert.throws(() => { sharp(fixtures.inputJpg).dilate(-1); }); }); diff --git a/test/unit/erode.js b/test/unit/erode.js index 4d2da81f0..0afa874df 100644 --- a/test/unit/erode.js +++ b/test/unit/erode.js @@ -1,15 +1,14 @@ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Erode', function () { - it('erode 1 png', function (done) { +describe('Erode', () => { + it('erode 1 png', (_t, done) => { sharp(fixtures.inputPngDotAndLines) .erode(1) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(100, info.width); @@ -18,10 +17,10 @@ describe('Erode', function () { }); }); - it('erode 1 png - default width', function (done) { + it('erode 1 png - default width', (_t, done) => { sharp(fixtures.inputPngDotAndLines) .erode() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(100, info.width); @@ -30,8 +29,8 @@ describe('Erode', function () { }); }); - it('invalid erosion width', function () { - assert.throws(function () { + it('invalid erosion width', () => { + assert.throws(() => { sharp(fixtures.inputJpg).erode(-1); }); }); diff --git a/test/unit/extend.js b/test/unit/extend.js index 3ddbd0e14..f9d91112a 100644 --- a/test/unit/extend.js +++ b/test/unit/extend.js @@ -1,20 +1,21 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Extend', function () { - describe('extend all sides equally via a single value', function () { - it('JPEG', function (done) { +describe('Extend', () => { + describe('extend all sides equally via a single value', () => { + it('JPEG', (_t, done) => { sharp(fixtures.inputJpg) .resize(120) .extend(10) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(140, info.width); assert.strictEqual(118, info.height); @@ -22,11 +23,11 @@ describe('Extend', function () { }); }); - it('Animated WebP', function (done) { + it('Animated WebP', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(120) .extend(10) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(140, info.width); assert.strictEqual(140 * 9, info.height); @@ -36,7 +37,7 @@ describe('Extend', function () { }); ['background', 'copy', 'mirror', 'repeat'].forEach(extendWith => { - it(`extends all sides with animated WebP (${extendWith})`, function (done) { + it(`extends all sides with animated WebP (${extendWith})`, (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(120) .extend({ @@ -46,7 +47,7 @@ describe('Extend', function () { left: 40, right: 40 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(200, info.width); assert.strictEqual(200 * 9, info.height); @@ -54,7 +55,7 @@ describe('Extend', function () { }); }); - it(`extend all sides equally with RGB (${extendWith})`, function (done) { + it(`extend all sides equally with RGB (${extendWith})`, (_t, done) => { sharp(fixtures.inputJpg) .resize(120) .extend({ @@ -65,7 +66,7 @@ describe('Extend', function () { right: 10, background: { r: 255, g: 0, b: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(140, info.width); assert.strictEqual(118, info.height); @@ -73,7 +74,7 @@ describe('Extend', function () { }); }); - it(`extend sides unequally with RGBA (${extendWith})`, function (done) { + it(`extend sides unequally with RGBA (${extendWith})`, (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .resize(120) .extend({ @@ -83,7 +84,7 @@ describe('Extend', function () { right: 35, background: { r: 0, g: 0, b: 0, alpha: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(165, info.width); assert.strictEqual(170, info.height); @@ -91,7 +92,7 @@ describe('Extend', function () { }); }); - it(`PNG with 2 channels (${extendWith})`, function (done) { + it(`PNG with 2 channels (${extendWith})`, (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .extend({ extendWith, @@ -101,7 +102,7 @@ describe('Extend', function () { right: 80, background: 'transparent' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -146,13 +147,13 @@ describe('Extend', function () { assert.strictEqual(1470, height); }); - it('missing parameter fails', function () { - assert.throws(function () { + it('missing parameter fails', () => { + assert.throws(() => { sharp().extend(); }); }); - it('negative fails', function () { - assert.throws(function () { + it('negative fails', () => { + assert.throws(() => { sharp().extend(-1); }); }); @@ -190,7 +191,7 @@ describe('Extend', function () { assert.doesNotThrow(() => sharp().extend({ top: 1, left: 2, bottom: 3 })); }); - it('should add alpha channel before extending with a transparent Background', function (done) { + it('should add alpha channel before extending with a transparent Background', (_t, done) => { sharp(fixtures.inputJpgWithLandscapeExif1) .extend({ bottom: 10, @@ -198,7 +199,7 @@ describe('Extend', function () { background: { r: 0, g: 0, b: 0, alpha: 0 } }) .toFormat(sharp.format.png) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(610, info.width); assert.strictEqual(460, info.height); diff --git a/test/unit/extract.js b/test/unit/extract.js index c2eae90b4..4fee38f08 100644 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -1,18 +1,19 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Partial image extraction', function () { - it('JPEG', function (done) { +describe('Partial image extraction', () => { + it('JPEG', (_t, done) => { sharp(fixtures.inputJpg) .extract({ left: 2, top: 2, width: 20, height: 20 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(20, info.width); assert.strictEqual(20, info.height); @@ -20,10 +21,10 @@ describe('Partial image extraction', function () { }); }); - it('PNG', function (done) { + it('PNG', (_t, done) => { sharp(fixtures.inputPng) .extract({ left: 200, top: 300, width: 400, height: 200 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(400, info.width); assert.strictEqual(200, info.height); @@ -31,10 +32,10 @@ describe('Partial image extraction', function () { }); }); - it('WebP', function (done) { + it('WebP', (_t, done) => { sharp(fixtures.inputWebP) .extract({ left: 100, top: 50, width: 125, height: 200 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(125, info.width); assert.strictEqual(200, info.height); @@ -42,12 +43,12 @@ describe('Partial image extraction', function () { }); }); - describe('Animated WebP', function () { - it('Before resize', function (done) { + describe('Animated WebP', () => { + it('Before resize', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .extract({ left: 0, top: 30, width: 80, height: 20 }) .resize(320, 80) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(80 * 9, info.height); @@ -55,11 +56,11 @@ describe('Partial image extraction', function () { }); }); - it('After resize', function (done) { + it('After resize', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(320, 320) .extract({ left: 0, top: 120, width: 320, height: 80 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(80 * 9, info.height); @@ -68,10 +69,10 @@ describe('Partial image extraction', function () { }); }); - it('TIFF', function (done) { + it('TIFF', (_t, done) => { sharp(fixtures.inputTiff) .extract({ left: 34, top: 63, width: 341, height: 529 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(341, info.width); assert.strictEqual(529, info.height); @@ -79,11 +80,11 @@ describe('Partial image extraction', function () { }); }); - it('Before resize', function (done) { + it('Before resize', (_t, done) => { sharp(fixtures.inputJpg) .extract({ left: 10, top: 10, width: 10, height: 500 }) .resize(100, 100) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(100, info.width); assert.strictEqual(100, info.height); @@ -91,13 +92,13 @@ describe('Partial image extraction', function () { }); }); - it('After resize and crop', function (done) { + it('After resize and crop', (_t, done) => { sharp(fixtures.inputJpg) .resize(500, 500, { position: sharp.gravity.north }) .extract({ left: 10, top: 10, width: 100, height: 100 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(100, info.width); assert.strictEqual(100, info.height); @@ -105,14 +106,14 @@ describe('Partial image extraction', function () { }); }); - it('Before and after resize and crop', function (done) { + it('Before and after resize and crop', (_t, done) => { sharp(fixtures.inputJpg) .extract({ left: 0, top: 0, width: 700, height: 700 }) .resize(500, 500, { position: sharp.gravity.north }) .extract({ left: 10, top: 10, width: 100, height: 100 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(100, info.width); assert.strictEqual(100, info.height); @@ -120,12 +121,12 @@ describe('Partial image extraction', function () { }); }); - it('Extract then rotate', function (done) { + it('Extract then rotate', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .extract({ left: 20, top: 10, width: 380, height: 280 }) .rotate(90) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(280, info.width); assert.strictEqual(380, info.height); @@ -133,11 +134,11 @@ describe('Partial image extraction', function () { }); }); - it('Rotate then extract', function (done) { + it('Rotate then extract', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .rotate(90) .extract({ left: 20, top: 10, width: 280, height: 380 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(280, info.width); assert.strictEqual(380, info.height); @@ -145,12 +146,12 @@ describe('Partial image extraction', function () { }); }); - it('Extract then rotate then extract', function (done) { + it('Extract then rotate then extract', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .extract({ left: 20, top: 10, width: 180, height: 280 }) .rotate(90) .extract({ left: 20, top: 10, width: 200, height: 100 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(200, info.width); assert.strictEqual(100, info.height); @@ -158,12 +159,12 @@ describe('Partial image extraction', function () { }); }); - it('Extract then rotate non-90 anagle', function (done) { + it('Extract then rotate non-90 anagle', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .extract({ left: 20, top: 10, width: 380, height: 280 }) .rotate(45) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(467, info.width); assert.strictEqual(467, info.height); @@ -171,12 +172,12 @@ describe('Partial image extraction', function () { }); }); - it('Rotate then extract non-90 angle', function (done) { + it('Rotate then extract non-90 angle', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .rotate(45) .extract({ left: 20, top: 10, width: 380, height: 280 }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(380, info.width); assert.strictEqual(280, info.height); @@ -219,11 +220,11 @@ describe('Partial image extraction', function () { image: fixtures.inputJpgWithLandscapeExif8 } ].forEach(({ name, image }) => { - it(name, function (done) { + it(name, (_t, done) => { sharp(image) .rotate() .extract({ left: 0, top: 208, width: 60, height: 40 }) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done); }); @@ -231,67 +232,67 @@ describe('Partial image extraction', function () { }); }); - describe('Invalid parameters', function () { - describe('using the legacy extract(top,left,width,height) syntax', function () { - it('String top', function () { - assert.throws(function () { + describe('Invalid parameters', () => { + describe('using the legacy extract(top,left,width,height) syntax', () => { + it('String top', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10); }); }); - it('Non-integral left', function () { - assert.throws(function () { + it('Non-integral left', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10); }); }); - it('Negative width - negative', function () { - assert.throws(function () { + it('Negative width - negative', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract(10, 10, -10, 10); }); }); - it('Null height', function () { - assert.throws(function () { + it('Null height', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract(10, 10, 10, null); }); }); }); - it('Undefined', function () { - assert.throws(function () { + it('Undefined', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract(); }); }); - it('String top', function () { - assert.throws(function () { + it('String top', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract({ left: 10, top: 'spoons', width: 10, height: 10 }); }); }); - it('Non-integral left', function () { - assert.throws(function () { + it('Non-integral left', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract({ left: 10.2, top: 10, width: 10, height: 10 }); }); }); - it('Negative width - negative', function () { - assert.throws(function () { + it('Negative width - negative', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: -10, height: 10 }); }); }); - it('Null height', function () { - assert.throws(function () { + it('Null height', () => { + assert.throws(() => { sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: 10, height: null }); }); }); - it('Bad image area', function (done) { + it('Bad image area', (_t, done) => { sharp(fixtures.inputJpg) .extract({ left: 3000, top: 10, width: 10, height: 10 }) - .toBuffer(function (err) { + .toBuffer((err) => { assert(err instanceof Error); assert.strictEqual(err.message, 'extract_area: bad extract area'); done(); @@ -301,7 +302,7 @@ describe('Partial image extraction', function () { it('Multiple extract emits warning', () => { let warningMessage = ''; const s = sharp(); - s.on('warning', function (msg) { warningMessage = msg; }); + s.on('warning', (msg) => { warningMessage = msg; }); const options = { top: 0, left: 0, width: 1, height: 1 }; s.extract(options).extract(options); assert.strictEqual(warningMessage, ''); @@ -312,7 +313,7 @@ describe('Partial image extraction', function () { it('Multiple rotate+extract emits warning', () => { let warningMessage = ''; const s = sharp().rotate(); - s.on('warning', function (msg) { warningMessage = msg; }); + s.on('warning', (msg) => { warningMessage = msg; }); const options = { top: 0, left: 0, width: 1, height: 1 }; s.extract(options).extract(options); assert.strictEqual(warningMessage, ''); @@ -323,7 +324,7 @@ describe('Partial image extraction', function () { it('Multiple extract+resize emits warning', () => { let warningMessage = ''; const s = sharp(); - s.on('warning', function (msg) { warningMessage = msg; }); + s.on('warning', (msg) => { warningMessage = msg; }); const options = { top: 0, left: 0, width: 1, height: 1 }; s.extract(options).extract(options); assert.strictEqual(warningMessage, ''); diff --git a/test/unit/extractChannel.js b/test/unit/extractChannel.js index 59c53600d..761b816df 100644 --- a/test/unit/extractChannel.js +++ b/test/unit/extractChannel.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Image channel extraction', function () { - it('Red channel', function (done) { +describe('Image channel extraction', () => { + it('Red channel', (_t, done) => { sharp(fixtures.inputJpg) .extractChannel('red') .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -21,11 +22,11 @@ describe('Image channel extraction', function () { }); }); - it('Green channel', function (done) { + it('Green channel', (_t, done) => { sharp(fixtures.inputJpg) .extractChannel('green') .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -33,11 +34,11 @@ describe('Image channel extraction', function () { }); }); - it('Blue channel', function (done) { + it('Blue channel', (_t, done) => { sharp(fixtures.inputJpg) .extractChannel('blue') .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -45,11 +46,11 @@ describe('Image channel extraction', function () { }); }); - it('Blue channel by number', function (done) { + it('Blue channel by number', (_t, done) => { sharp(fixtures.inputJpg) .extractChannel(2) .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -66,24 +67,23 @@ describe('Image channel extraction', function () { assert.strictEqual(chroma, 104); }); - it('Alpha from 16-bit PNG', function (done) { + it('Alpha from 16-bit PNG', (_t, done) => { const output = fixtures.path('output.extract-alpha-16bit.png'); sharp(fixtures.inputPngWithTransparency16bit) .resize(16) .extractChannel(3) - .toFile(output, function (err) { + .toFile(output, (err) => { if (err) throw err; fixtures.assertMaxColourDistance(output, fixtures.expected('extract-alpha-16bit.png')); done(); }); }); - it('Alpha from 2-channel input', function (done) { + it('Alpha from 2-channel input', (_t, done) => { const output = fixtures.path('output.extract-alpha-2-channel.png'); sharp(fixtures.inputPngWithGreyAlpha) .extractChannel('alpha') - .toColourspace('b-w') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(1, info.channels); fixtures.assertMaxColourDistance(output, fixtures.expected('extract-alpha-2-channel.png')); @@ -91,15 +91,15 @@ describe('Image channel extraction', function () { }); }); - it('Invalid channel number', function () { - assert.throws(function () { + it('Invalid channel number', () => { + assert.throws(() => { sharp(fixtures.inputJpg) .extractChannel(-1); }); }); - it('No arguments', function () { - assert.throws(function () { + it('No arguments', () => { + assert.throws(() => { sharp(fixtures.inputJpg) .extractChannel(); }); diff --git a/test/unit/failOn.js b/test/unit/failOn.js index 650ba057f..c4d44e892 100644 --- a/test/unit/failOn.js +++ b/test/unit/failOn.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); -const fs = require('fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); const sharp = require('../../lib'); const fixtures = require('../fixtures'); describe('failOn', () => { - it('handles truncated JPEG', function (done) { + it('handles truncated JPEG', (_t, done) => { sharp(fixtures.inputJpgTruncated, { failOn: 'none' }) .resize(32, 24) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(32, info.width); @@ -22,17 +23,17 @@ describe('failOn', () => { }); }); - it('handles truncated PNG, emits warnings', function (done) { + it('handles truncated PNG, emits warnings', (_t, done) => { let isWarningEmitted = false; sharp(fixtures.inputPngTruncated, { failOn: 'none' }) - .on('warning', function (warning) { + .on('warning', (warning) => { assert.ok( ['read gave 2 warnings', 'not enough data', 'end of stream'] .some(m => warning.includes(m))); isWarningEmitted = true; }) .resize(32, 24) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(true, isWarningEmitted); assert.strictEqual('png', info.format); @@ -70,8 +71,8 @@ describe('failOn', () => { ); }); - it('returns errors to callback for truncated JPEG', function (done) { - sharp(fixtures.inputJpgTruncated, { failOn: 'truncated' }).toBuffer(function (err, data, info) { + it('returns errors to callback for truncated JPEG', (_t, done) => { + sharp(fixtures.inputJpgTruncated, { failOn: 'truncated' }).toBuffer((err, data, info) => { assert.ok(err.message.includes('VipsJpeg: premature end of'), err); assert.strictEqual(data, undefined); assert.strictEqual(info, undefined); @@ -79,8 +80,8 @@ describe('failOn', () => { }); }); - it('returns errors to callback for truncated PNG', function (done) { - sharp(fixtures.inputPngTruncated, { failOn: 'truncated' }).toBuffer(function (err, data, info) { + it('returns errors to callback for truncated PNG', (_t, done) => { + sharp(fixtures.inputPngTruncated, { failOn: 'truncated' }).toBuffer((err, data, info) => { assert.ok(err.message.includes('read error'), err); assert.strictEqual(data, undefined); assert.strictEqual(info, undefined); @@ -88,7 +89,7 @@ describe('failOn', () => { }); }); - it('rejects promises for truncated JPEG', function (done) { + it('rejects promises for truncated JPEG', (_t, done) => { sharp(fixtures.inputJpgTruncated, { failOn: 'error' }) .toBuffer() .then(() => { @@ -104,4 +105,11 @@ describe('failOn', () => { fs.createReadStream(fixtures.inputJpgTruncated).pipe(writable); return writable.toBuffer(); }); + + it('converts warnings to error for GeoTIFF', async () => { + await assert.rejects( + sharp(fixtures.inputTiffGeo).toBuffer(), + /Tag 34737/ + ); + }); }); diff --git a/test/unit/fixtures.js b/test/unit/fixtures.js index fb0ee74ee..9d4583ea2 100644 --- a/test/unit/fixtures.js +++ b/test/unit/fixtures.js @@ -1,28 +1,29 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); -describe('Test fixtures', function () { - describe('assertMaxColourDistance', function () { - it('should throw an Error when images have a different number of channels', function () { - assert.throws(function () { +describe('Test fixtures', () => { + describe('assertMaxColourDistance', () => { + it('should throw an Error when images have a different number of channels', () => { + assert.throws(() => { fixtures.assertMaxColourDistance(fixtures.inputPngOverlayLayer1, fixtures.inputJpg); }); }); - it('should throw an Error when images have different dimensions', function () { - assert.throws(function () { + it('should throw an Error when images have different dimensions', () => { + assert.throws(() => { fixtures.assertMaxColourDistance(fixtures.inputJpg, fixtures.inputJpgWithExif); }); }); - it('should accept a zero threshold when comparing an image to itself', function () { + it('should accept a zero threshold when comparing an image to itself', () => { const image = fixtures.inputPngOverlayLayer0; fixtures.assertMaxColourDistance(image, image, 0); }); - it('should accept a numeric threshold for two different images', function () { + it('should accept a numeric threshold for two different images', () => { fixtures.assertMaxColourDistance(fixtures.inputPngOverlayLayer0, fixtures.inputPngOverlayLayer1, 100); }); }); diff --git a/test/unit/gamma.js b/test/unit/gamma.js index 934a4519b..2e0b3fcde 100644 --- a/test/unit/gamma.js +++ b/test/unit/gamma.js @@ -1,18 +1,19 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Gamma correction', function () { - it('value of 0.0 (disabled)', function (done) { +describe('Gamma correction', () => { + it('value of 0.0 (disabled)', (_t, done) => { sharp(fixtures.inputJpgWithGammaHoliness) .resize(129, 111) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(129, info.width); @@ -21,11 +22,11 @@ describe('Gamma correction', function () { }); }); - it('value of 2.2 (default)', function (done) { + it('value of 2.2 (default)', (_t, done) => { sharp(fixtures.inputJpgWithGammaHoliness) .resize(129, 111) .gamma() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(129, info.width); @@ -34,11 +35,11 @@ describe('Gamma correction', function () { }); }); - it('value of 3.0', function (done) { + it('value of 3.0', (_t, done) => { sharp(fixtures.inputJpgWithGammaHoliness) .resize(129, 111) .gamma(3) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(129, info.width); @@ -47,11 +48,11 @@ describe('Gamma correction', function () { }); }); - it('input value of 2.2, output value of 3.0', function (done) { + it('input value of 2.2, output value of 3.0', (_t, done) => { sharp(fixtures.inputJpgWithGammaHoliness) .resize(129, 111) .gamma(2.2, 3.0) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(129, info.width); @@ -60,12 +61,12 @@ describe('Gamma correction', function () { }); }); - it('alpha transparency', function (done) { + it('alpha transparency', (_t, done) => { sharp(fixtures.inputPngOverlayLayer1) .resize(320) .gamma() .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -73,14 +74,14 @@ describe('Gamma correction', function () { }); }); - it('invalid first parameter value', function () { - assert.throws(function () { + it('invalid first parameter value', () => { + assert.throws(() => { sharp(fixtures.inputJpgWithGammaHoliness).gamma(4); }); }); - it('invalid second parameter value', function () { - assert.throws(function () { + it('invalid second parameter value', () => { + assert.throws(() => { sharp(fixtures.inputJpgWithGammaHoliness).gamma(2.2, 4); }); }); diff --git a/test/unit/gif.js b/test/unit/gif.js index 93e4d46b4..4b7d4a76a 100644 --- a/test/unit/gif.js +++ b/test/unit/gif.js @@ -1,10 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); @@ -187,11 +188,22 @@ describe('GIF input', () => { ); }); - it('should work with streams when only animated is set', function (done) { + 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', (_t, done) => { fs.createReadStream(fixtures.inputGifAnimated) .pipe(sharp({ animated: true })) .gif() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('gif', info.format); @@ -199,11 +211,11 @@ describe('GIF input', () => { }); }); - it('should work with streams when only pages is set', function (done) { + it('should work with streams when only pages is set', (_t, done) => { fs.createReadStream(fixtures.inputGifAnimated) .pipe(sharp({ pages: -1 })) .gif() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('gif', info.format); @@ -225,6 +237,20 @@ describe('GIF input', () => { 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 () => { for (const input of [fixtures.inputGif, fixtures.inputPng]) { const data = await sharp(input) diff --git a/test/unit/heif.js b/test/unit/heif.js index be304b3dd..adb7b2969 100644 --- a/test/unit/heif.js +++ b/test/unit/heif.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); diff --git a/test/unit/io.js b/test/unit/io.js index 98b96a571..ad13f48b6 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -1,29 +1,30 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); +const fs = require('node:fs'); +const path = require('node:path'); +const { afterEach, beforeEach, describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); const outputJpg = fixtures.path('output.jpg'); -describe('Input/output', function () { - beforeEach(function () { +describe('Input/output', () => { + beforeEach(() => { sharp.cache(false); }); - afterEach(function () { + afterEach(() => { sharp.cache(true); }); - it('Read from File and write to Stream', function (done) { + it('Read from File and write to Stream', (_t, done) => { const writable = fs.createWriteStream(outputJpg); - writable.on('close', function () { - sharp(outputJpg).toBuffer(function (err, data, info) { + writable.on('close', () => { + sharp(outputJpg).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -36,11 +37,11 @@ describe('Input/output', function () { sharp(fixtures.inputJpg).resize(320, 240).pipe(writable); }); - it('Read from Buffer and write to Stream', function (done) { + it('Read from Buffer and write to Stream', (_t, done) => { const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); - writable.on('close', function () { - sharp(outputJpg).toBuffer(function (err, data, info) { + writable.on('close', () => { + sharp(outputJpg).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -53,9 +54,9 @@ describe('Input/output', function () { sharp(inputJpgBuffer).resize(320, 240).pipe(writable); }); - it('Read from Stream and write to File', function (done) { + it('Read from Stream and write to File', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); - const pipeline = sharp().resize(320, 240).toFile(outputJpg, function (err, info) { + const pipeline = sharp().resize(320, 240).toFile(outputJpg, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('jpeg', info.format); @@ -66,9 +67,9 @@ describe('Input/output', function () { readable.pipe(pipeline); }); - it('Read from Stream and write to Buffer', function (done) { + it('Read from Stream and write to Buffer', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); - const pipeline = sharp().resize(320, 240).toBuffer(function (err, data, info) { + const pipeline = sharp().resize(320, 240).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -80,23 +81,23 @@ describe('Input/output', function () { readable.pipe(pipeline); }); - it('Read from Stream and write to Buffer via Promise resolved with Buffer', function () { + it('Read from Stream and write to Buffer via Promise resolved with Buffer', () => { const pipeline = sharp().resize(1, 1); fs.createReadStream(fixtures.inputJpg).pipe(pipeline); return pipeline .toBuffer({ resolveWithObject: false }) - .then(function (data) { + .then((data) => { assert.strictEqual(true, data instanceof Buffer); assert.strictEqual(true, data.length > 0); }); }); - it('Read from Stream and write to Buffer via Promise resolved with Object', function () { + it('Read from Stream and write to Buffer via Promise resolved with Object', () => { const pipeline = sharp().resize(1, 1); fs.createReadStream(fixtures.inputJpg).pipe(pipeline); return pipeline .toBuffer({ resolveWithObject: true }) - .then(function (object) { + .then((object) => { assert.strictEqual('object', typeof object); assert.strictEqual('object', typeof object.info); assert.strictEqual('jpeg', object.info.format); @@ -108,21 +109,18 @@ describe('Input/output', function () { }); }); - it('Read from File and write to Buffer via Promise resolved with Buffer', function () { - return sharp(fixtures.inputJpg) + it('Read from File and write to Buffer via Promise resolved with Buffer', () => sharp(fixtures.inputJpg) .resize(1, 1) .toBuffer({ resolveWithObject: false }) - .then(function (data) { + .then((data) => { assert.strictEqual(true, data instanceof Buffer); assert.strictEqual(true, data.length > 0); - }); - }); + })); - it('Read from File and write to Buffer via Promise resolved with Object', function () { - return sharp(fixtures.inputJpg) + it('Read from File and write to Buffer via Promise resolved with Object', () => sharp(fixtures.inputJpg) .resize(1, 1) .toBuffer({ resolveWithObject: true }) - .then(function (object) { + .then((object) => { assert.strictEqual('object', typeof object); assert.strictEqual('object', typeof object.info); assert.strictEqual('jpeg', object.info.format); @@ -131,14 +129,13 @@ describe('Input/output', function () { assert.strictEqual(3, object.info.channels); assert.strictEqual(true, object.data instanceof Buffer); assert.strictEqual(true, object.data.length > 0); - }); - }); + })); - it('Read from Stream and write to Stream', function (done) { + it('Read from Stream and write to Stream', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); - writable.on('close', function () { - sharp(outputJpg).toBuffer(function (err, data, info) { + writable.on('close', () => { + sharp(outputJpg).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -219,46 +216,46 @@ describe('Input/output', function () { assert.strictEqual(info.height, 1); }); - it('Stream should emit info event', function (done) { + it('Stream should emit info event', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); const pipeline = sharp().resize(320, 240); let infoEventEmitted = false; - pipeline.on('info', function (info) { + pipeline.on('info', (info) => { assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); assert.strictEqual(3, info.channels); infoEventEmitted = true; }); - writable.on('close', function () { + writable.on('close', () => { assert.strictEqual(true, infoEventEmitted); fs.rm(outputJpg, done); }); readable.pipe(pipeline).pipe(writable); }); - it('Stream should emit close event', function (done) { + it('Stream should emit close event', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); const pipeline = sharp().resize(320, 240); let closeEventEmitted = false; - pipeline.on('close', function () { + pipeline.on('close', () => { closeEventEmitted = true; }); - writable.on('close', function () { + writable.on('close', () => { assert.strictEqual(true, closeEventEmitted); fs.rm(outputJpg, done); }); readable.pipe(pipeline).pipe(writable); }); - it('Handle Stream to Stream error ', function (done) { + it('Handle Stream to Stream error ', (_t, done) => { const pipeline = sharp().resize(320, 240); let anErrorWasEmitted = false; - pipeline.on('error', function (err) { + pipeline.on('error', (err) => { anErrorWasEmitted = !!err; - }).on('end', function () { + }).on('end', () => { assert(anErrorWasEmitted); fs.rm(outputJpg, done); }); @@ -267,12 +264,12 @@ describe('Input/output', function () { readableButNotAnImage.pipe(pipeline).pipe(writable); }); - it('Handle File to Stream error', function (done) { + it('Handle File to Stream error', (_t, done) => { const readableButNotAnImage = sharp(__filename).resize(320, 240); let anErrorWasEmitted = false; - readableButNotAnImage.on('error', function (err) { + readableButNotAnImage.on('error', (err) => { anErrorWasEmitted = !!err; - }).on('end', function () { + }).on('end', () => { assert(anErrorWasEmitted); fs.rm(outputJpg, done); }); @@ -280,11 +277,11 @@ describe('Input/output', function () { readableButNotAnImage.pipe(writable); }); - it('Readable side of Stream can start flowing after Writable side has finished', function (done) { + it('Readable side of Stream can start flowing after Writable side has finished', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); const writable = fs.createWriteStream(outputJpg); - writable.on('close', function () { - sharp(outputJpg).toBuffer(function (err, data, info) { + writable.on('close', () => { + sharp(outputJpg).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -296,11 +293,25 @@ describe('Input/output', function () { }); const pipeline = sharp().resize(320, 240); readable.pipe(pipeline); - pipeline.on('finish', function () { + pipeline.on('finish', () => { pipeline.pipe(writable); }); }); + it('Non-Stream input generates error when provided Stream-like data', (_t, done) => { + sharp('input')._write('fail', null, (err) => { + assert.strictEqual(err.message, 'Unexpected data on Writable Stream'); + done(); + }); + }); + + it('Non-Buffer chunk on Stream input generates error', (_t, done) => { + sharp()._write('fail', null, (err) => { + assert.strictEqual(err.message, 'Non-Buffer data on Writable Stream'); + done(); + }); + }); + it('Invalid sequential read option throws', () => { assert.throws(() => { sharp({ sequentialRead: 'fail' }); @@ -335,11 +346,11 @@ describe('Input/output', function () { }) ); - it('Support output to jpg format', function (done) { + it('Support output to jpg format', (_t, done) => { sharp(fixtures.inputPng) .resize(320, 240) .toFormat('jpg') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -350,11 +361,11 @@ describe('Input/output', function () { }); }); - it('Support output to tif format', function (done) { + it('Support output to tif format', (_t, done) => { sharp(fixtures.inputTiff) .resize(320, 240) .toFormat('tif') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -379,80 +390,76 @@ describe('Input/output', function () { assert.strictEqual(Buffer.isBuffer(data), true); }); - it('Fail when output File is input File', function (done) { - sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) { + it('Fail when output File is input File', (_t, done) => { + sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, (err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is input File via Promise', function (done) { - sharp(fixtures.inputJpg).toFile(fixtures.inputJpg).then(function (data) { - assert(false); - done(); - }).catch(function (err) { + it('Fail when output File is input File via Promise', (_t, done) => { + sharp(fixtures.inputJpg).toFile(fixtures.inputJpg).then(() => { + done(new Error('Unexpectedly resolved Promise')); + }).catch((err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is input File (relative output, absolute input)', function (done) { + it('Fail when output File is input File (relative output, absolute input)', (_t, done) => { const relativePath = path.relative(process.cwd(), fixtures.inputJpg); - sharp(fixtures.inputJpg).toFile(relativePath, function (err) { + sharp(fixtures.inputJpg).toFile(relativePath, (err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is input File via Promise (relative output, absolute input)', function (done) { + it('Fail when output File is input File via Promise (relative output, absolute input)', (_t, done) => { const relativePath = path.relative(process.cwd(), fixtures.inputJpg); - sharp(fixtures.inputJpg).toFile(relativePath).then(function (data) { - assert(false); - done(); - }).catch(function (err) { + sharp(fixtures.inputJpg).toFile(relativePath).then(() => { + done(new Error('Unexpectedly resolved Promise')); + }).catch((err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is input File (relative input, absolute output)', function (done) { + it('Fail when output File is input File (relative input, absolute output)', (_t, done) => { const relativePath = path.relative(process.cwd(), fixtures.inputJpg); - sharp(relativePath).toFile(fixtures.inputJpg, function (err) { + sharp(relativePath).toFile(fixtures.inputJpg, (err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is input File via Promise (relative input, absolute output)', function (done) { + it('Fail when output File is input File via Promise (relative input, absolute output)', (_t, done) => { const relativePath = path.relative(process.cwd(), fixtures.inputJpg); - sharp(relativePath).toFile(fixtures.inputJpg).then(function (data) { - assert(false); - done(); - }).catch(function (err) { + sharp(relativePath).toFile(fixtures.inputJpg).then(() => { + done(new Error('Unexpectedly resolved Promise')); + }).catch((err) => { assert(err instanceof Error); assert.strictEqual('Cannot use same file for input and output', err.message); done(); }); }); - it('Fail when output File is empty', function (done) { - sharp(fixtures.inputJpg).toFile('', function (err) { + it('Fail when output File is empty', (_t, done) => { + sharp(fixtures.inputJpg).toFile('', (err) => { assert(err instanceof Error); assert.strictEqual('Missing output file path', err.message); done(); }); }); - it('Fail when output File is empty via Promise', function (done) { - sharp(fixtures.inputJpg).toFile('').then(function (data) { - assert(false); - done(); - }).catch(function (err) { + it('Fail when output File is empty via Promise', (_t, done) => { + sharp(fixtures.inputJpg).toFile('').then(() => { + done(new Error('Unexpectedly resolved Promise')); + }).catch((err) => { assert(err instanceof Error); assert.strictEqual('Missing output file path', err.message); done(); @@ -483,74 +490,72 @@ describe('Input/output', function () { ) ); - describe('Fail for unsupported input', function () { - it('Undefined', function () { - assert.throws(function () { + describe('Fail for unsupported input', () => { + it('Undefined', () => { + assert.throws(() => { sharp(undefined); }); }); - it('Null', function () { - assert.throws(function () { + it('Null', () => { + assert.throws(() => { sharp(null); }); }); - it('Numeric', function () { - assert.throws(function () { + it('Numeric', () => { + assert.throws(() => { sharp(1); }); }); - it('Boolean', function () { - assert.throws(function () { + it('Boolean', () => { + assert.throws(() => { sharp(true); }); }); - it('Error Object', function () { - assert.throws(function () { + it('Error Object', () => { + assert.throws(() => { sharp(new Error()); }); }); }); - it('Promises/A+', function () { - return sharp(fixtures.inputJpg) + it('Promises/A+', () => sharp(fixtures.inputJpg) .resize(320, 240) - .toBuffer(); - }); + .toBuffer()); - it('Invalid output format', function (done) { + it('Invalid output format', (_t, done) => { let isValid = false; try { sharp().toFormat('zoinks'); isValid = true; - } catch (e) {} + } catch (_err) {} assert(!isValid); done(); }); - it('File input with corrupt header fails gracefully', function (done) { + it('File input with corrupt header fails gracefully', (_t, done) => { sharp(fixtures.inputJpgWithCorruptHeader) - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, !!err); done(); }); }); - it('Buffer input with corrupt header fails gracefully', function (done) { + it('Buffer input with corrupt header fails gracefully', (_t, done) => { sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader)) - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, !!err); done(); }); }); - it('Stream input with corrupt header fails gracefully', function (done) { + it('Stream input with corrupt header fails gracefully', (_t, done) => { const transformer = sharp(); transformer .toBuffer() - .then(function () { + .then(() => { done(new Error('Unexpectedly resolved Promise')); }) - .catch(function (err) { + .catch((err) => { assert.strictEqual(true, !!err); done(); }); @@ -559,13 +564,13 @@ describe('Input/output', function () { .pipe(transformer); }); - describe('Output filename with unknown extension', function () { + describe('Output filename with unknown extension', () => { const outputZoinks = fixtures.path('output.zoinks'); - it('Match JPEG input', function (done) { + it('Match JPEG input', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 80) - .toFile(outputZoinks, function (err, info) { + .toFile(outputZoinks, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('jpeg', info.format); @@ -575,10 +580,10 @@ describe('Input/output', function () { }); }); - it('Match PNG input', function (done) { + it('Match PNG input', (_t, done) => { sharp(fixtures.inputPng) .resize(320, 80) - .toFile(outputZoinks, function (err, info) { + .toFile(outputZoinks, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('png', info.format); @@ -588,10 +593,10 @@ describe('Input/output', function () { }); }); - it('Match WebP input', function (done) { + it('Match WebP input', (_t, done) => { sharp(fixtures.inputWebP) .resize(320, 80) - .toFile(outputZoinks, function (err, info) { + .toFile(outputZoinks, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('webp', info.format); @@ -601,10 +606,10 @@ describe('Input/output', function () { }); }); - it('Match TIFF input', function (done) { + it('Match TIFF input', (_t, done) => { sharp(fixtures.inputTiff) .resize(320, 80) - .toFile(outputZoinks, function (err, info) { + .toFile(outputZoinks, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('tiff', info.format); @@ -614,11 +619,11 @@ describe('Input/output', function () { }); }); - it('Force JPEG format for PNG input', function (done) { + it('Force JPEG format for PNG input', (_t, done) => { sharp(fixtures.inputPng) .resize(320, 80) .jpeg() - .toFile(outputZoinks, function (err, info) { + .toFile(outputZoinks, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('jpeg', info.format); @@ -629,11 +634,11 @@ describe('Input/output', function () { }); }); - it('Input and output formats match when not forcing', function (done) { + it('Input and output formats match when not forcing', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .png({ compressionLevel: 1, force: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -642,44 +647,42 @@ describe('Input/output', function () { }); }); - it('Can force output format with output chaining', function () { - return sharp(fixtures.inputJpg) + it('Can force output format with output chaining', () => sharp(fixtures.inputJpg) .resize(320, 240) .png({ force: true }) .jpeg({ force: false }) .toBuffer({ resolveWithObject: true }) - .then(function (out) { + .then((out) => { assert.strictEqual('png', out.info.format); - }); - }); + })); - it('toFormat=JPEG takes precedence over WebP extension', function (done) { + it('toFormat=JPEG takes precedence over WebP extension', (_t, done) => { const outputWebP = fixtures.path('output.webp'); sharp(fixtures.inputPng) .resize(8) .jpeg() - .toFile(outputWebP, function (err, info) { + .toFile(outputWebP, (err, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); fs.rm(outputWebP, done); }); }); - it('toFormat=WebP takes precedence over JPEG extension', function (done) { + it('toFormat=WebP takes precedence over JPEG extension', (_t, done) => { sharp(fixtures.inputPng) .resize(8) .webp() - .toFile(outputJpg, function (err, info) { + .toFile(outputJpg, (err, info) => { if (err) throw err; assert.strictEqual('webp', info.format); done(); }); }); - it('Load Vips V file', function (done) { + it('Load Vips V file', (_t, done) => { sharp(fixtures.inputV) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -689,11 +692,11 @@ describe('Input/output', function () { }); }); - it('Save Vips V file', function (done) { + it('Save Vips V file', (_t, done) => { const outputV = fixtures.path('output.v'); sharp(fixtures.inputJpg) .extract({ left: 910, top: 1105, width: 70, height: 60 }) - .toFile(outputV, function (err, info) { + .toFile(outputV, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); assert.strictEqual('v', info.format); @@ -816,103 +819,142 @@ describe('Input/output', function () { ); }); - describe('Input options', function () { - it('Option-less', function () { + describe('Input options', () => { + it('Option-less', () => { sharp(); }); - it('Ignore unknown attribute', function () { + it('Ignore unknown attribute', () => { sharp({ unknown: true }); }); - it('undefined with options fails', function () { - assert.throws(function () { + it('undefined with options fails', () => { + assert.throws(() => { sharp(undefined, {}); }, /Unsupported input 'undefined' of type undefined when also providing options of type object/); }); - it('null with options fails', function () { - assert.throws(function () { + it('null with options fails', () => { + assert.throws(() => { sharp(null, {}); }, /Unsupported input 'null' of type object when also providing options of type object/); }); - it('Non-Object options fails', function () { - assert.throws(function () { + it('Non-Object options fails', () => { + assert.throws(() => { sharp('test', 'zoinks'); }, /Invalid input options zoinks/); }); - it('Invalid density: string', function () { - assert.throws(function () { + it('Invalid density: string', () => { + assert.throws(() => { sharp({ density: 'zoinks' }); }, /Expected number between 1 and 100000 for density but received zoinks of type string/); }); - it('Invalid ignoreIcc: string', function () { - assert.throws(function () { + it('Invalid ignoreIcc: string', () => { + assert.throws(() => { sharp({ ignoreIcc: 'zoinks' }); }, /Expected boolean for ignoreIcc but received zoinks of type string/); }); - it('Setting animated property updates pages property', function () { + it('Setting animated property updates pages property', () => { assert.strictEqual(sharp({ animated: false }).options.input.pages, 1); assert.strictEqual(sharp({ animated: true }).options.input.pages, -1); }); - it('Invalid animated property throws', function () { - assert.throws(function () { + it('Invalid animated property throws', () => { + assert.throws(() => { sharp({ animated: -1 }); }, /Expected boolean for animated but received -1 of type number/); }); - it('Invalid page property throws', function () { - assert.throws(function () { + it('Invalid page property throws', () => { + assert.throws(() => { sharp({ page: -1 }); }, /Expected integer between 0 and 100000 for page but received -1 of type number/); }); - it('Invalid pages property throws', function () { - assert.throws(function () { + it('Invalid pages property throws', () => { + assert.throws(() => { sharp({ pages: '1' }); }, /Expected integer between -1 and 100000 for pages but received 1 of type string/); }); - it('Valid level property', function () { + it('Valid openSlide.level property', () => { + sharp({ openSlide: { level: 1 } }); sharp({ level: 1 }); }); - it('Invalid level property (string) throws', function () { - assert.throws(function () { - sharp({ level: '1' }); - }, /Expected integer between 0 and 256 for level but received 1 of type string/); - }); - it('Invalid level property (negative) throws', function () { - assert.throws(function () { - sharp({ level: -1 }); - }, /Expected integer between 0 and 256 for level but received -1 of type number/); - }); - it('Valid subifd property', function () { + it('Invalid openSlide.level property (string) throws', () => { + assert.throws( + () => sharp({ openSlide: { level: '1' } }), + /Expected integer between 0 and 256 for openSlide.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', () => { + assert.throws( + () => sharp({ openSlide: { level: -1 } }), + /Expected integer between 0 and 256 for openSlide\.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', () => { + sharp({ tiff: { subifd: 1 } }); sharp({ subifd: 1 }); }); - it('Invalid subifd property (string) throws', function () { - assert.throws(function () { - sharp({ subifd: '1' }); - }, /Expected integer between -1 and 100000 for subifd but received 1 of type string/); - }); - it('Invalid subifd property (float) throws', function () { - assert.throws(function () { - sharp({ subifd: 1.2 }); - }, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/); - }); - it('Valid pdfBackground property (string)', function () { + it('Invalid tiff.subifd property (string) throws', () => { + assert.throws( + () => sharp({ tiff: { subifd: '1' } }), + /Expected integer between -1 and 100000 for tiff\.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', () => { + assert.throws( + () => sharp({ tiff: { subifd: 1.2 } }), + /Expected integer between -1 and 100000 for tiff\.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)', () => { + sharp({ pdf: { background: '#00ff00' } }); sharp({ pdfBackground: '#00ff00' }); }); - it('Valid pdfBackground property (object)', function () { + it('Valid pdf.background property (object)', () => { + sharp({ pdf: { background: { r: 0, g: 255, b: 0 } } }); sharp({ pdfBackground: { r: 0, g: 255, b: 0 } }); }); - it('Invalid pdfBackground property (string) throws', function () { - assert.throws(function () { - sharp({ pdfBackground: '00ff00' }); - }, /Unable to parse color from string/); - }); - it('Invalid pdfBackground property (number) throws', function () { - assert.throws(function () { - sharp({ pdfBackground: 255 }); - }, /Expected object or string for background/); - }); - it('Invalid pdfBackground property (object)', function () { - assert.throws(function () { - sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } }); - }, /Unable to parse color from object/); + it('Invalid pdf.background property (string) throws', () => { + assert.throws( + () => sharp({ pdf: { background: '00ff00' } }), + /Unable to parse color from string/ + ); + assert.throws( + () => sharp({ pdfBackground: '00ff00' }), + /Unable to parse color from string/ + ); + }); + it('Invalid pdf.background property (number) throws', () => { + assert.throws( + () => sharp({ pdf: { background: 255 } }), + /Expected object or string for background/ + ); + assert.throws( + () => sharp({ pdf: { background: 255 } }), + /Expected object or string for background/ + ); + }); + it('Invalid pdf.background property (object)', () => { + assert.throws( + () => sharp({ pdf: { background: { red: 0, green: 255, blue: 0 } } }), + /Unable to parse color from object/ + ); + assert.throws( + () => sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } }), + /Unable to parse color from object/ + ); }); }); @@ -929,8 +971,8 @@ describe('Input/output', function () { ); }); - describe('create new image', function () { - it('RGB', function (done) { + describe('create new image', () => { + it('RGB', (_t, done) => { const create = { width: 10, height: 20, @@ -939,7 +981,7 @@ describe('Input/output', function () { }; sharp({ create }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(create.width, info.width); assert.strictEqual(create.height, info.height); @@ -948,7 +990,7 @@ describe('Input/output', function () { fixtures.assertSimilar(fixtures.expected('create-rgb.jpg'), data, done); }); }); - it('RGBA', function (done) { + it('RGBA', (_t, done) => { const create = { width: 20, height: 10, @@ -957,7 +999,7 @@ describe('Input/output', function () { }; sharp({ create }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(create.width, info.width); assert.strictEqual(create.height, info.height); @@ -966,40 +1008,40 @@ describe('Input/output', function () { fixtures.assertSimilar(fixtures.expected('create-rgba.png'), data, done); }); }); - it('Invalid channels', function () { + it('Invalid channels', () => { const create = { width: 10, height: 20, channels: 2, background: { r: 0, g: 0, b: 0 } }; - assert.throws(function () { + assert.throws(() => { sharp({ create }); }); }); - it('Missing background', function () { + it('Missing background', () => { const create = { width: 10, height: 20, channels: 3 }; - assert.throws(function () { + assert.throws(() => { sharp({ create }); }); }); }); - it('Queue length change events', function (done) { + it('Queue length change events', (_t, done) => { let eventCounter = 0; - const queueListener = function (queueLength) { + const queueListener = (queueLength) => { assert.strictEqual(true, queueLength === 0 || queueLength === 1); eventCounter++; }; sharp.queue.on('change', queueListener); sharp(fixtures.inputJpg) .resize(320, 240) - .toBuffer(function (err) { - process.nextTick(function () { + .toBuffer((err) => { + process.nextTick(() => { sharp.queue.removeListener('change', queueListener); if (err) throw err; assert.strictEqual(2, eventCounter); @@ -1008,19 +1050,19 @@ describe('Input/output', function () { }); }); - it('Info event data', function (done) { + it('Info event data', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJPGBig); const inPipeline = sharp() .resize(840, 472) .raw() - .on('info', function (info) { + .on('info', (info) => { assert.strictEqual(840, info.width); assert.strictEqual(472, info.height); assert.strictEqual(3, info.channels); }); const badPipeline = sharp({ raw: { width: 840, height: 500, channels: 3 } }) .toFormat('jpeg') - .toBuffer(function (err, data, info) { + .toBuffer((err) => { assert.strictEqual(err.message.indexOf('memory area too small') > 0, true); const readable = fs.createReadStream(fixtures.inputJPGBig); const inPipeline = sharp() @@ -1028,12 +1070,26 @@ describe('Input/output', function () { .raw(); const goodPipeline = sharp({ raw: { width: 840, height: 472, channels: 3 } }) .toFormat('jpeg') - .toBuffer(function (err, data, info) { - if (err) throw err; - done(); - }); + .toBuffer(done); readable.pipe(inPipeline).pipe(goodPipeline); }); 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'); + }); }); diff --git a/test/unit/join.js b/test/unit/join.js index 0e14e97e4..c95c35626 100644 --- a/test/unit/join.js +++ b/test/unit/join.js @@ -1,14 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Join input images together', function () { +describe('Join input images together', () => { it('Join two images horizontally', async () => { const data = await sharp([ fixtures.inputPngPalette, diff --git a/test/unit/joinChannel.js b/test/unit/joinChannel.js index acc0e7676..feebb1337 100644 --- a/test/unit/joinChannel.js +++ b/test/unit/joinChannel.js @@ -1,21 +1,22 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); -const fs = require('fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Image channel insertion', function () { - it('Grayscale to RGB, buffer', function (done) { +describe('Image channel insertion', () => { + it('Grayscale to RGB, buffer', (_t, done) => { sharp(fixtures.inputPng) // gray -> red .resize(320, 240) .joinChannel(fixtures.inputPngTestJoinChannel) // new green channel .joinChannel(fixtures.inputPngStripesH) // new blue channel - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -24,12 +25,12 @@ describe('Image channel insertion', function () { }); }); - it('Grayscale to RGB, file', function (done) { + it('Grayscale to RGB, file', (_t, done) => { sharp(fixtures.inputPng) // gray -> red .resize(320, 240) .joinChannel(fs.readFileSync(fixtures.inputPngTestJoinChannel)) // new green channel .joinChannel(fs.readFileSync(fixtures.inputPngStripesH)) // new blue channel - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -38,7 +39,7 @@ describe('Image channel insertion', function () { }); }); - it('Grayscale to RGBA, buffer', function (done) { + it('Grayscale to RGBA, buffer', (_t, done) => { sharp(fixtures.inputPng) // gray -> red .resize(320, 240) .joinChannel([ @@ -47,7 +48,7 @@ describe('Image channel insertion', function () { fixtures.inputPngStripesV ]) // new green + blue + alpha channel .toColourspace(sharp.colourspace.srgb) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -56,7 +57,7 @@ describe('Image channel insertion', function () { }); }); - it('Grayscale to RGBA, file', function (done) { + it('Grayscale to RGBA, file', (_t, done) => { sharp(fixtures.inputPng) // gray -> red .resize(320, 240) .joinChannel([ @@ -65,7 +66,7 @@ describe('Image channel insertion', function () { fs.readFileSync(fixtures.inputPngStripesV) // new alpha channel ]) .toColourspace('srgb') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -74,7 +75,7 @@ describe('Image channel insertion', function () { }); }); - it('Grayscale to CMYK, buffers', function (done) { + it('Grayscale to CMYK, buffers', (_t, done) => { sharp(fixtures.inputPng) // gray -> magenta .resize(320, 240) .joinChannel([ @@ -84,7 +85,7 @@ describe('Image channel insertion', function () { ]) .toColorspace('cmyk') .toFormat('jpeg') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -93,12 +94,12 @@ describe('Image channel insertion', function () { }); }); - it('Join raw buffers to RGB', function (done) { + it('Join raw buffers to RGB', (_t, done) => { Promise.all([ sharp(fixtures.inputPngTestJoinChannel).toColourspace('b-w').raw().toBuffer(), sharp(fixtures.inputPngStripesH).toColourspace('b-w').raw().toBuffer() ]) - .then(function (buffers) { + .then((buffers) => { sharp(fixtures.inputPng) .resize(320, 240) .joinChannel(buffers, { @@ -108,7 +109,7 @@ describe('Image channel insertion', function () { channels: 1 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -116,12 +117,12 @@ describe('Image channel insertion', function () { fixtures.assertSimilar(fixtures.expected('joinChannel-rgb.jpg'), data, done); }); }) - .catch(function (err) { + .catch((err) => { throw err; }); }); - it('Grayscale to RGBA, files, two arrays', function (done) { + it('Grayscale to RGBA, files, two arrays', (_t, done) => { sharp(fixtures.inputPng) // gray -> red .resize(320, 240) .joinChannel([fs.readFileSync(fixtures.inputPngTestJoinChannel)]) // new green channel @@ -130,7 +131,7 @@ describe('Image channel insertion', function () { fs.readFileSync(fixtures.inputPngStripesV) // new alpha channel ]) .toColourspace('srgb') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -139,20 +140,20 @@ describe('Image channel insertion', function () { }); }); - it('Invalid raw buffer description', function () { - assert.throws(function () { + it('Invalid raw buffer description', () => { + assert.throws(() => { sharp().joinChannel(fs.readFileSync(fixtures.inputPng), { raw: {} }); }); }); - it('Invalid input', function () { - assert.throws(function () { + it('Invalid input', () => { + assert.throws(() => { sharp(fixtures.inputJpg).joinChannel(1); }); }); - it('No arguments', function () { - assert.throws(function () { + it('No arguments', () => { + assert.throws(() => { sharp(fixtures.inputJpg).joinChannel(); }); }); diff --git a/test/unit/jp2.js b/test/unit/jp2.js index c70f1543a..536e0a040 100644 --- a/test/unit/jp2.js +++ b/test/unit/jp2.js @@ -1,10 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); @@ -44,19 +45,19 @@ describe('JP2 output', () => { assert.strictEqual('png', info.format); assert.strictEqual(8, info.width); assert.strictEqual(15, info.height); - assert.strictEqual(4, info.channels); + assert.strictEqual(3, info.channels); }); }); - it('JP2 quality', function (done) { + it('JP2 quality', (_t, done) => { sharp(fixtures.inputJp2) .resize(320, 240) .jp2({ quality: 70 }) - .toBuffer(function (err, buffer70) { + .toBuffer((err, buffer70) => { if (err) throw err; sharp(fixtures.inputJp2) .resize(320, 240) - .toBuffer(function (err, buffer80) { + .toBuffer((err, buffer80) => { if (err) throw err; assert(buffer70.length < buffer80.length); done(); @@ -64,12 +65,12 @@ describe('JP2 output', () => { }); }); - it('Without chroma subsampling generates larger file', function (done) { + it('Without chroma subsampling generates larger file', (_t, done) => { // First generate with chroma subsampling (default) sharp(fixtures.inputJp2) .resize(320, 240) .jp2({ chromaSubsampling: '4:2:0' }) - .toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) { + .toBuffer((err, withChromaSubsamplingData, withChromaSubsamplingInfo) => { if (err) throw err; assert.strictEqual(true, withChromaSubsamplingData.length > 0); assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size); @@ -80,7 +81,7 @@ describe('JP2 output', () => { sharp(fixtures.inputJp2) .resize(320, 240) .jp2({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) { + .toBuffer((err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) => { if (err) throw err; assert.strictEqual(true, withoutChromaSubsamplingData.length > 0); assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size); @@ -93,10 +94,38 @@ describe('JP2 output', () => { }); }); - it('Invalid JP2 chromaSubsampling value throws error', function () { - assert.throws(function () { - sharp().jpeg({ chromaSubsampling: '4:2:2' }); + it('can use the jp2Oneshot option to handle multi-part tiled JPEG 2000 file', async () => { + const outputJpg = fixtures.path('output.jpg'); + await assert.rejects( + () => 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 for chromaSubsampling 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/ + ); + }); }); diff --git a/test/unit/jpeg.js b/test/unit/jpeg.js index 6f7ca978f..9a802cb8f 100644 --- a/test/unit/jpeg.js +++ b/test/unit/jpeg.js @@ -1,28 +1,29 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('JPEG', function () { - it('JPEG quality', function (done) { +describe('JPEG', () => { + it('JPEG quality', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ quality: 70 }) - .toBuffer(function (err, buffer70) { + .toBuffer((err, buffer70) => { if (err) throw err; sharp(fixtures.inputJpg) .resize(320, 240) - .toBuffer(function (err, buffer80) { + .toBuffer((err, buffer80) => { if (err) throw err; sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ quality: 90 }) - .toBuffer(function (err, buffer90) { + .toBuffer((err, buffer90) => { if (err) throw err; assert(buffer70.length < buffer80.length); assert(buffer80.length < buffer90.length); @@ -32,31 +33,31 @@ describe('JPEG', function () { }); }); - describe('Invalid JPEG quality', function () { - [-1, 88.2, 'test'].forEach(function (quality) { - it(quality.toString(), function () { - assert.throws(function () { + describe('Invalid JPEG quality', () => { + [-1, 88.2, 'test'].forEach((quality) => { + it(quality.toString(), () => { + assert.throws(() => { sharp().jpeg({ quality }); }); }); }); }); - describe('Invalid JPEG quantisation table', function () { - [-1, 88.2, 'test'].forEach(function (table) { - it(table.toString(), function () { - assert.throws(function () { + describe('Invalid JPEG quantisation table', () => { + [-1, 88.2, 'test'].forEach((table) => { + it(table.toString(), () => { + assert.throws(() => { sharp().jpeg({ quantisationTable: table }); }); }); }); }); - it('Progressive JPEG image', function (done) { + it('Progressive JPEG image', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ progressive: false }) - .toBuffer(function (err, nonProgressiveData, nonProgressiveInfo) { + .toBuffer((err, nonProgressiveData, nonProgressiveInfo) => { if (err) throw err; assert.strictEqual(true, nonProgressiveData.length > 0); assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size); @@ -66,7 +67,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ progressive: true }) - .toBuffer(function (err, progressiveData, progressiveInfo) { + .toBuffer((err, progressiveData, progressiveInfo) => { if (err) throw err; assert.strictEqual(true, progressiveData.length > 0); assert.strictEqual(progressiveData.length, progressiveInfo.size); @@ -79,12 +80,12 @@ describe('JPEG', function () { }); }); - it('Without chroma subsampling generates larger file', function (done) { + it('Without chroma subsampling generates larger file', (_t, done) => { // First generate with chroma subsampling (default) sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ chromaSubsampling: '4:2:0' }) - .toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) { + .toBuffer((err, withChromaSubsamplingData, withChromaSubsamplingInfo) => { if (err) throw err; assert.strictEqual(true, withChromaSubsamplingData.length > 0); assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size); @@ -95,7 +96,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) { + .toBuffer((err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) => { if (err) throw err; assert.strictEqual(true, withoutChromaSubsamplingData.length > 0); assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size); @@ -108,18 +109,18 @@ describe('JPEG', function () { }); }); - it('Invalid JPEG chromaSubsampling value throws error', function () { - assert.throws(function () { + it('Invalid JPEG chromaSubsampling value throws error', () => { + assert.throws(() => { sharp().jpeg({ chromaSubsampling: '4:2:2' }); }); }); - it('Trellis quantisation', function (done) { + it('Trellis quantisation', (_t, done) => { // First generate without sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ trellisQuantisation: false }) - .toBuffer(function (err, withoutData, withoutInfo) { + .toBuffer((err, withoutData, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withoutData.length > 0); assert.strictEqual(withoutData.length, withoutInfo.size); @@ -130,7 +131,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ trellisQuantization: true }) - .toBuffer(function (err, withData, withInfo) { + .toBuffer((err, withData, withInfo) => { if (err) throw err; assert.strictEqual(true, withData.length > 0); assert.strictEqual(withData.length, withInfo.size); @@ -144,12 +145,12 @@ describe('JPEG', function () { }); }); - it('Overshoot deringing', function (done) { + it('Overshoot deringing', (_t, done) => { // First generate without sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ overshootDeringing: false }) - .toBuffer(function (err, withoutData, withoutInfo) { + .toBuffer((err, withoutData, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withoutData.length > 0); assert.strictEqual(withoutData.length, withoutInfo.size); @@ -160,7 +161,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ overshootDeringing: true }) - .toBuffer(function (err, withData, withInfo) { + .toBuffer((err, withData, withInfo) => { if (err) throw err; assert.strictEqual(true, withData.length > 0); assert.strictEqual(withData.length, withInfo.size); @@ -172,12 +173,12 @@ describe('JPEG', function () { }); }); - it('Optimise scans generates different output length', function (done) { + it('Optimise scans generates different output length', (_t, done) => { // First generate without sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimiseScans: false }) - .toBuffer(function (err, withoutData, withoutInfo) { + .toBuffer((err, withoutData, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withoutData.length > 0); assert.strictEqual(withoutData.length, withoutInfo.size); @@ -188,7 +189,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimizeScans: true }) - .toBuffer(function (err, withData, withInfo) { + .toBuffer((err, withData, withInfo) => { if (err) throw err; assert.strictEqual(true, withData.length > 0); assert.strictEqual(withData.length, withInfo.size); @@ -202,12 +203,12 @@ describe('JPEG', function () { }); }); - it('Optimise coding generates smaller output length', function (done) { + it('Optimise coding generates smaller output length', (_t, done) => { // First generate with optimize coding enabled (default) sharp(fixtures.inputJpg) .resize(320, 240) .jpeg() - .toBuffer(function (err, withOptimiseCoding, withInfo) { + .toBuffer((err, withOptimiseCoding, withInfo) => { if (err) throw err; assert.strictEqual(true, withOptimiseCoding.length > 0); assert.strictEqual(withOptimiseCoding.length, withInfo.size); @@ -218,7 +219,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimizeCoding: false }) - .toBuffer(function (err, withoutOptimiseCoding, withoutInfo) { + .toBuffer((err, withoutOptimiseCoding, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withoutOptimiseCoding.length > 0); assert.strictEqual(withoutOptimiseCoding.length, withoutInfo.size); @@ -232,12 +233,12 @@ describe('JPEG', function () { }); }); - it('Specifying quantisation table provides different JPEG', function (done) { + it('Specifying quantisation table provides different JPEG', (_t, done) => { // First generate with default quantisation table sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimiseCoding: false }) - .toBuffer(function (err, withDefaultQuantisationTable, withInfo) { + .toBuffer((err, withDefaultQuantisationTable, withInfo) => { if (err) throw err; assert.strictEqual(true, withDefaultQuantisationTable.length > 0); assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size); @@ -248,7 +249,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimiseCoding: false, quantisationTable: 3 }) - .toBuffer(function (err, withQuantTable3, withoutInfo) { + .toBuffer((err, withQuantTable3, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withQuantTable3.length > 0); assert.strictEqual(withQuantTable3.length, withoutInfo.size); @@ -263,12 +264,12 @@ describe('JPEG', function () { }); }); - it('Specifying quantization table provides different JPEG', function (done) { + it('Specifying quantization table provides different JPEG', (_t, done) => { // First generate with default quantization table sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimiseCoding: false }) - .toBuffer(function (err, withDefaultQuantizationTable, withInfo) { + .toBuffer((err, withDefaultQuantizationTable, withInfo) => { if (err) throw err; assert.strictEqual(true, withDefaultQuantizationTable.length > 0); assert.strictEqual(withDefaultQuantizationTable.length, withInfo.size); @@ -279,7 +280,7 @@ describe('JPEG', function () { sharp(fixtures.inputJpg) .resize(320, 240) .jpeg({ optimiseCoding: false, quantizationTable: 3 }) - .toBuffer(function (err, withQuantTable3, withoutInfo) { + .toBuffer((err, withQuantTable3, withoutInfo) => { if (err) throw err; assert.strictEqual(true, withQuantTable3.length > 0); assert.strictEqual(withQuantTable3.length, withoutInfo.size); diff --git a/test/unit/jxl.js b/test/unit/jxl.js index 32200407c..f70de1cbb 100644 --- a/test/unit/jxl.js +++ b/test/unit/jxl.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); diff --git a/test/unit/libvips.js b/test/unit/libvips.js index 5578ff42a..8ed822458 100644 --- a/test/unit/libvips.js +++ b/test/unit/libvips.js @@ -1,64 +1,65 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const assert = require('assert'); -const fs = require('fs'); +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ + +const { after, before, describe, it } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); const semver = require('semver'); const libvips = require('../../lib/libvips'); const originalPlatform = process.platform; -const setPlatform = function (platform) { +const setPlatform = (platform) => { Object.defineProperty(process, 'platform', { value: platform }); }; -const restorePlatform = function () { +const restorePlatform = () => { setPlatform(originalPlatform); }; -describe('libvips binaries', function () { - describe('Windows platform', function () { - before(function () { setPlatform('win32'); }); +describe('libvips binaries', () => { + describe('Windows platform', () => { + before(() => { setPlatform('win32'); }); after(restorePlatform); - it('pkgConfigPath returns empty string', function () { + it('pkgConfigPath returns empty string', () => { assert.strictEqual('', libvips.pkgConfigPath()); }); - it('globalLibvipsVersion returns empty string', function () { + it('globalLibvipsVersion returns empty string', () => { assert.strictEqual('', libvips.globalLibvipsVersion()); }); - it('globalLibvipsVersion is always false', function () { + it('globalLibvipsVersion is always false', () => { assert.strictEqual(false, libvips.useGlobalLibvips()); }); }); - describe('non-Windows platforms', function () { - before(function () { setPlatform('linux'); }); + describe('non-Windows platforms', () => { + before(() => { setPlatform('linux'); }); after(restorePlatform); - it('pkgConfigPath returns a string', function () { + it('pkgConfigPath returns a string', () => { const pkgConfigPath = libvips.pkgConfigPath(); assert.strictEqual('string', typeof pkgConfigPath); }); - it('globalLibvipsVersion returns a string', function () { + it('globalLibvipsVersion returns a string', () => { const globalLibvipsVersion = libvips.globalLibvipsVersion(); assert.strictEqual('string', typeof globalLibvipsVersion); }); - it('globalLibvipsVersion returns a boolean', function () { + it('globalLibvipsVersion returns a boolean', () => { const useGlobalLibvips = libvips.useGlobalLibvips(); assert.strictEqual('boolean', typeof useGlobalLibvips); }); }); - describe('platform agnostic', function () { - it('minimumLibvipsVersion returns a valid semver', function () { + describe('platform agnostic', () => { + it('minimumLibvipsVersion returns a valid semver', () => { const minimumLibvipsVersion = libvips.minimumLibvipsVersion; assert.strictEqual('string', typeof minimumLibvipsVersion); assert.notStrictEqual(null, semver.valid(minimumLibvipsVersion)); }); - it('useGlobalLibvips can be ignored via an env var', function () { + it('useGlobalLibvips can be ignored via an env var', () => { process.env.SHARP_IGNORE_GLOBAL_LIBVIPS = 1; const useGlobalLibvips = libvips.useGlobalLibvips(); @@ -66,14 +67,14 @@ describe('libvips binaries', function () { delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS; }); - it('useGlobalLibvips can be forced via an env var', function () { + it('useGlobalLibvips can be forced via an env var', () => { process.env.SHARP_FORCE_GLOBAL_LIBVIPS = 1; const useGlobalLibvips = libvips.useGlobalLibvips(); assert.strictEqual(true, useGlobalLibvips); let logged = false; - const logger = function (message) { + const logger = (message) => { assert.strictEqual(message, 'Detected SHARP_FORCE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips'); logged = true; }; @@ -145,25 +146,25 @@ describe('libvips binaries', function () { }); }); - describe('logger', function () { + describe('logger', () => { const consoleLog = console.log; const consoleError = console.error; - after(function () { + after(() => { console.log = consoleLog; console.error = consoleError; }); - it('logs an info message', function (done) { - console.log = function (msg) { + it('logs an info message', (_t, done) => { + console.log = (msg) => { assert.strictEqual(msg, 'sharp: progress'); done(); }; libvips.log('progress'); }); - it('logs an error message', function (done) { - console.error = function (msg) { + it('logs an error message', (_t, done) => { + console.error = (msg) => { assert.strictEqual(msg, 'sharp: Installation error: problem'); done(); }; @@ -179,7 +180,7 @@ describe('libvips binaries', function () { process.env.npm_config_arch = 's390x'; process.env.npm_config_libc = ''; const locatorHash = libvips.yarnLocator(); - assert.strictEqual(locatorHash, '9b2ea457de'); + assert.strictEqual(locatorHash, '4ab19140fd'); delete process.env.npm_config_platform; delete process.env.npm_config_arch; delete process.env.npm_config_libc; diff --git a/test/unit/linear.js b/test/unit/linear.js index d668aabdf..c6d29a7de 100644 --- a/test/unit/linear.js +++ b/test/unit/linear.js @@ -1,89 +1,90 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const sharp = require('../../'); const fixtures = require('../fixtures'); -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); -describe('Linear adjustment', function () { +describe('Linear adjustment', () => { const blackPoint = 70; const whitePoint = 203; const a = 255 / (whitePoint - blackPoint); const b = -blackPoint * a; - it('applies linear levels adjustment w/o alpha ch', function (done) { + it('applies linear levels adjustment w/o alpha ch', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .linear(a, b) - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('low-contrast-linear.jpg'), data, done); }); }); - it('applies slope level adjustment w/o alpha ch', function (done) { + it('applies slope level adjustment w/o alpha ch', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .linear(a) - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('low-contrast-slope.jpg'), data, done); }); }); - it('applies offset level adjustment w/o alpha ch', function (done) { + it('applies offset level adjustment w/o alpha ch', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .linear(null, b) - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('low-contrast-offset.jpg'), data, done); }); }); - it('applies linear levels adjustment w alpha ch', function (done) { + it('applies linear levels adjustment w alpha ch', (_t, done) => { sharp(fixtures.inputPngOverlayLayer1) .resize(240) .linear(a, b) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-linear.png'), data, done); }); }); - it('applies linear levels adjustment to 16-bit w alpha ch', function (done) { + it('applies linear levels adjustment to 16-bit w alpha ch', (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .linear(a, b) .png({ compressionLevel: 0 }) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('linear-16bit.png'), data, done); }); }); - it('applies slope level adjustment w alpha ch', function (done) { + it('applies slope level adjustment w alpha ch', (_t, done) => { sharp(fixtures.inputPngOverlayLayer1) .resize(240) .linear(a) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-slope.png'), data, done); }); }); - it('applies offset level adjustment w alpha ch', function (done) { + it('applies offset level adjustment w alpha ch', (_t, done) => { sharp(fixtures.inputPngOverlayLayer1) .resize(240) .linear(null, b) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-offset.png'), data, done); }); }); - it('per channel level adjustment', function (done) { + it('per channel level adjustment', (_t, done) => { sharp(fixtures.inputWebP) - .linear([0.25, 0.5, 0.75], [150, 100, 50]).toBuffer(function (err, data, info) { + .linear([0.25, 0.5, 0.75], [150, 100, 50]).toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('linear-per-channel.jpg'), data, done); }); @@ -111,7 +112,7 @@ describe('Linear adjustment', function () { assert.strictEqual(depth, 'uchar'); }); - it('Invalid linear arguments', function () { + it('Invalid linear arguments', () => { assert.throws( () => sharp().linear('foo'), /Expected number or array of numbers for a but received foo of type string/ diff --git a/test/unit/median.js b/test/unit/median.js index d674bb19e..14d63d566 100644 --- a/test/unit/median.js +++ b/test/unit/median.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); @@ -15,7 +16,7 @@ const raw = { channels: 1 }; -describe('Median filter', function () { +describe('Median filter', () => { it('default window (3x3)', async () => { const data = await sharp(input, { raw }) .median() @@ -43,7 +44,7 @@ describe('Median filter', function () { .raw() .toBuffer(); - assert.deepStrictEqual(data.subarray(0, 6), Buffer.from([0, 3, 15, 15, 63, 127])); + assert.deepStrictEqual(data.subarray(0, 6), Buffer.from(row)); }); it('invalid radius', () => { diff --git a/test/unit/metadata.js b/test/unit/metadata.js index b5bb5e59d..300380edd 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -1,10 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ + +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const exifReader = require('exif-reader'); const icc = require('icc'); @@ -13,9 +14,9 @@ const fixtures = require('../fixtures'); const create = { width: 1, height: 1, channels: 3, background: 'red' }; -describe('Image metadata', function () { - it('JPEG', function (done) { - sharp(fixtures.inputJpg).metadata(function (err, metadata) { +describe('Image metadata', () => { + it('JPEG', (_t, done) => { + sharp(fixtures.inputJpg).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('jpeg', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -36,8 +37,8 @@ describe('Image metadata', function () { }); }); - it('JPEG with EXIF/ICC', function (done) { - sharp(fixtures.inputJpgWithExif).metadata(function (err, metadata) { + it('JPEG with EXIF/ICC', (_t, done) => { + sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('jpeg', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -69,8 +70,8 @@ describe('Image metadata', function () { }); }); - it('JPEG with IPTC/XMP', function (done) { - sharp(fixtures.inputJpgWithIptcAndXmp).metadata(function (err, metadata) { + it('JPEG with IPTC/XMP', (_t, done) => { + sharp(fixtures.inputJpgWithIptcAndXmp).metadata((err, metadata) => { if (err) throw err; // IPTC assert.strictEqual('object', typeof metadata.iptc); @@ -82,12 +83,13 @@ describe('Image metadata', function () { assert.strictEqual(true, metadata.xmp instanceof Buffer); assert.strictEqual(12466, metadata.xmp.byteLength); assert.strictEqual(metadata.xmp.indexOf(Buffer.from('')); done(); }); }); - it('TIFF', function (done) { - sharp(fixtures.inputTiff).metadata(function (err, metadata) { + it('TIFF', (_t, done) => { + sharp(fixtures.inputTiff).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('tiff', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -106,13 +108,15 @@ describe('Image metadata', function () { assert.strictEqual(3248, metadata.autoOrient.height); assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); + assert.strictEqual('undefined', typeof metadata.xmp); + assert.strictEqual('undefined', typeof metadata.xmpAsString); assert.strictEqual('inch', metadata.resolutionUnit); done(); }); }); - it('Multipage TIFF', function (done) { - sharp(fixtures.inputTiffMultipage).metadata(function (err, metadata) { + it('Multipage TIFF', (_t, done) => { + sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('tiff', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -134,8 +138,8 @@ describe('Image metadata', function () { }); }); - it('PNG', function (done) { - sharp(fixtures.inputPng).metadata(function (err, metadata) { + it('PNG', (_t, done) => { + sharp(fixtures.inputPng).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('png', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -158,8 +162,8 @@ describe('Image metadata', function () { }); }); - it('PNG with comment', function (done) { - sharp(fixtures.inputPngTestJoinChannel).metadata(function (err, metadata) { + it('PNG with comment', (_t, done) => { + sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('png', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -183,8 +187,8 @@ describe('Image metadata', function () { }); }); - it('Transparent PNG', function (done) { - sharp(fixtures.inputPngWithTransparency).metadata(function (err, metadata) { + it('Transparent PNG', (_t, done) => { + sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('png', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -255,8 +259,8 @@ describe('Image metadata', function () { }); }); - it('WebP', function (done) { - sharp(fixtures.inputWebP).metadata(function (err, metadata) { + it('WebP', (_t, done) => { + sharp(fixtures.inputWebP).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('webp', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -347,8 +351,8 @@ describe('Image metadata', function () { }) ); - it('GIF', function (done) { - sharp(fixtures.inputGif).metadata(function (err, metadata) { + it('GIF', (_t, done) => { + sharp(fixtures.inputGif).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('gif', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -367,8 +371,8 @@ describe('Image metadata', function () { done(); }); }); - it('GIF grey+alpha', function (done) { - sharp(fixtures.inputGifGreyPlusAlpha).metadata(function (err, metadata) { + it('GIF grey+alpha', (_t, done) => { + sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('gif', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -455,8 +459,8 @@ describe('Image metadata', function () { }) ); - it('File in, Promise out', function (done) { - sharp(fixtures.inputJpg).metadata().then(function (metadata) { + it('File in, Promise out', (_t, done) => { + sharp(fixtures.inputJpg).metadata().then((metadata) => { assert.strictEqual('jpeg', metadata.format); assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual(2725, metadata.width); @@ -488,7 +492,7 @@ describe('Image metadata', function () { ) ); - it('Invalid stream in, callback out', (done) => { + it('Invalid stream in, callback out', (_t, done) => { fs.createReadStream(__filename).pipe( sharp().metadata((err) => { assert.strictEqual(err.message, 'Input buffer contains unsupported image format'); @@ -499,10 +503,10 @@ describe('Image metadata', function () { ); }); - it('Stream in, Promise out', function (done) { + it('Stream in, Promise out', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); const pipeline = sharp(); - pipeline.metadata().then(function (metadata) { + pipeline.metadata().then((metadata) => { assert.strictEqual('jpeg', metadata.format); assert.strictEqual(829183, metadata.size); assert.strictEqual(2725, metadata.width); @@ -519,7 +523,7 @@ describe('Image metadata', function () { assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); done(); - }).catch(done); + }).catch(_t, done); readable.pipe(pipeline); }); @@ -537,7 +541,7 @@ describe('Image metadata', function () { ); }); - it('Stream in, finish event fires before metadata is requested', (done) => { + it('Stream in, finish event fires before metadata is requested', (_t, done) => { const create = { width: 1, height: 1, channels: 3, background: 'red' }; const image1 = sharp({ create }).png().pipe(sharp()); const image2 = sharp({ create }).png().pipe(sharp()); @@ -550,9 +554,9 @@ describe('Image metadata', function () { }, 500); }); - it('Stream', function (done) { + it('Stream', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); - const pipeline = sharp().metadata(function (err, metadata) { + const pipeline = sharp().metadata((err, metadata) => { if (err) throw err; assert.strictEqual('jpeg', metadata.format); assert.strictEqual(829183, metadata.size); @@ -574,9 +578,9 @@ describe('Image metadata', function () { readable.pipe(pipeline); }); - it('Resize to half width using metadata', function (done) { + it('Resize to half width using metadata', (_t, done) => { const image = sharp(fixtures.inputJpg); - image.metadata(function (err, metadata) { + image.metadata((err, metadata) => { if (err) throw err; assert.strictEqual('jpeg', metadata.format); assert.strictEqual('undefined', typeof metadata.size); @@ -593,7 +597,7 @@ describe('Image metadata', function () { assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); - image.resize(Math.floor(metadata.width / 2)).toBuffer(function (err, data, info) { + image.resize(Math.floor(metadata.width / 2)).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(1362, info.width); @@ -603,13 +607,13 @@ describe('Image metadata', function () { }); }); - it('Keep EXIF metadata and add sRGB profile after a resize', function (done) { + it('Keep EXIF metadata and add sRGB profile after a resize', (_t, done) => { sharp(fixtures.inputJpgWithExif) .resize(320, 240) .withMetadata() - .toBuffer(function (err, buffer) { + .toBuffer((err, buffer) => { if (err) throw err; - sharp(buffer).metadata(function (err, metadata) { + sharp(buffer).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(8, metadata.orientation); @@ -647,16 +651,27 @@ describe('Image metadata', function () { assert.strictEqual(description, 'Generic RGB Profile'); }); - it('keep existing ICC profile, ignore colourspace conversion', async () => { - const data = await sharp(fixtures.inputJpgWithExif) + it('keep existing CMYK input profile for CMYK output', async () => { + const data = await sharp(fixtures.inputJpgWithCmykProfile) .keepIccProfile() .toColourspace('cmyk') .toBuffer(); const metadata = await sharp(data).metadata(); - assert.strictEqual(metadata.channels, 3); + assert.strictEqual(metadata.channels, 4); const { description } = icc.parse(metadata.icc); - assert.strictEqual(description, 'Generic RGB Profile'); + assert.strictEqual(description, 'U.S. Web Coated (SWOP) v2'); + }); + + it('transform with but discard existing RGB input profile for CMYK output', async () => { + const data = await sharp(fixtures.inputJpgWithExif) + .keepIccProfile() + .toColourspace('cmyk') + .toBuffer(); + + const metadata = await sharp(data).metadata(); + assert.strictEqual(metadata.channels, 4); + assert.strictEqual(metadata.icc, undefined); }); it('keep existing ICC profile, avoid colour transform', async () => { @@ -723,14 +738,14 @@ describe('Image metadata', function () { assert.strictEqual(undefined, metadata.icc); }); - it('Apply CMYK output ICC profile', function (done) { + it('Apply CMYK output ICC profile', (_t, done) => { const output = fixtures.path('output.icc-cmyk.jpg'); sharp(fixtures.inputJpg) .resize(64) .withIccProfile('cmyk') - .toFile(output, function (err) { + .toFile(output, (err) => { if (err) throw err; - sharp(output).metadata(function (err, metadata) { + sharp(output).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(true, metadata.hasProfile); assert.strictEqual('cmyk', metadata.space); @@ -748,12 +763,12 @@ describe('Image metadata', function () { }); }); - it('Apply custom output ICC profile', function (done) { + it('Apply custom output ICC profile', (_t, done) => { const output = fixtures.path('output.hilutite.jpg'); sharp(fixtures.inputJpg) .resize(64) .withIccProfile(fixtures.path('hilutite.icm')) - .toFile(output, function (err, info) { + .toFile(output, (err) => { if (err) throw err; fixtures.assertMaxColourDistance(output, fixtures.expected('hilutite.jpg'), 9); done(); @@ -788,12 +803,12 @@ describe('Image metadata', function () { ) ); - it('Remove EXIF metadata after a resize', function (done) { + it('Remove EXIF metadata after a resize', (_t, done) => { sharp(fixtures.inputJpgWithExif) .resize(320, 240) - .toBuffer(function (err, buffer) { + .toBuffer((err, buffer) => { if (err) throw err; - sharp(buffer).metadata(function (err, metadata) { + sharp(buffer).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(false, metadata.hasProfile); assert.strictEqual('undefined', typeof metadata.orientation); @@ -804,12 +819,12 @@ describe('Image metadata', function () { }); }); - it('Remove metadata from PNG output', function (done) { + it('Remove metadata from PNG output', (_t, done) => { sharp(fixtures.inputJpgWithExif) .png() - .toBuffer(function (err, buffer) { + .toBuffer((err, buffer) => { if (err) throw err; - sharp(buffer).metadata(function (err, metadata) { + sharp(buffer).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(false, metadata.hasProfile); assert.strictEqual('undefined', typeof metadata.orientation); @@ -861,55 +876,41 @@ describe('Image metadata', function () { assert.strictEqual(density, 96); }); - it('chromaSubsampling 4:4:4:4 CMYK JPEG', function () { - return sharp(fixtures.inputJpgWithCmykProfile) + it('chromaSubsampling 4:4:4:4 CMYK JPEG', () => sharp(fixtures.inputJpgWithCmykProfile) .metadata() - .then(function (metadata) { + .then((metadata) => { assert.strictEqual('4:4:4:4', metadata.chromaSubsampling); - }); - }); + })); - it('chromaSubsampling 4:4:4 RGB JPEG', function () { - return sharp(fixtures.inputJpg) + it('chromaSubsampling 4:4:4 RGB JPEG', () => sharp(fixtures.inputJpg) .resize(10, 10) .jpeg({ chromaSubsampling: '4:4:4' }) .toBuffer() - .then(function (data) { - return sharp(data) + .then((data) => sharp(data) .metadata() - .then(function (metadata) { + .then((metadata) => { assert.strictEqual('4:4:4', metadata.chromaSubsampling); - }); - }); - }); + }))); - it('isProgressive JPEG', function () { - return sharp(fixtures.inputJpg) + it('isProgressive JPEG', () => sharp(fixtures.inputJpg) .resize(10, 10) .jpeg({ progressive: true }) .toBuffer() - .then(function (data) { - return sharp(data) + .then((data) => sharp(data) .metadata() - .then(function (metadata) { + .then((metadata) => { assert.strictEqual(true, metadata.isProgressive); - }); - }); - }); + }))); - it('isProgressive PNG', function () { - return sharp(fixtures.inputJpg) + it('isProgressive PNG', () => sharp(fixtures.inputJpg) .resize(10, 10) .png({ progressive: true }) .toBuffer() - .then(function (data) { - return sharp(data) + .then((data) => sharp(data) .metadata() - .then(function (metadata) { + .then((metadata) => { assert.strictEqual(true, metadata.isProgressive); - }); - }); - }); + }))); it('16-bit TIFF with TIFFTAG_PHOTOSHOP metadata', () => sharp(fixtures.inputTifftagPhotoshop) @@ -991,31 +992,41 @@ describe('Image metadata', function () { assert.strictEqual(description, 'sP3C'); }); - it('File input with corrupt header fails gracefully', function (done) { + it('File input with corrupt header fails gracefully', (_t, done) => { sharp(fixtures.inputJpgWithCorruptHeader) - .metadata(function (err) { + .metadata((err) => { assert.strictEqual(true, !!err); assert.ok(err.message.includes('Input file has corrupt header: VipsJpeg: premature end of'), err); done(); }); }); - it('Buffer input with corrupt header fails gracefully', function (done) { + it('Buffer input with corrupt header fails gracefully', (_t, done) => { sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader)) - .metadata(function (err) { + .metadata((err) => { assert.strictEqual(true, !!err); assert.ok(err.message.includes('Input buffer has corrupt header: VipsJpeg: premature end of'), err); done(); }); }); - it('Unsupported lossless JPEG passes underlying error message', function (done) { - sharp(fixtures.inputJpgLossless) - .metadata(function (err) { - assert.strictEqual(true, !!err); - assert.strictEqual(true, /Input file has corrupt header: VipsJpeg: Unsupported JPEG process: SOF type 0xc3/.test(err.message)); - done(); - }); + it('Lossless JPEG', async () => { + const metadata = await sharp(fixtures.inputJpgLossless).metadata(); + assert.deepStrictEqual(metadata, { + format: 'jpeg', + width: 227, + height: 149, + space: 'srgb', + channels: 3, + depth: 'uchar', + density: 72, + chromaSubsampling: '4:4:4', + isProgressive: false, + isPalette: false, + hasProfile: false, + hasAlpha: false, + autoOrient: { width: 227, height: 149 } + }); }); it('keepExif maintains all EXIF metadata', async () => { @@ -1100,54 +1111,218 @@ describe('Image metadata', function () { assert.strictEqual(exif2.Image.Software, 'sharp'); }); - describe('Invalid parameters', function () { - it('String orientation', function () { - assert.throws(function () { + describe('XMP metadata tests', () => { + 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(' { + 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(' { + const customXmp = 'Test CreatorTest Title'; + + 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 = 'Added via Sharp'; + + 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 = 'testmetadata'; + + // 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 = 'persistent-test'; + + 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 = 'WebP Creatorimage/webp'; + + 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', () => { + assert.throws( + () => sharp().withXmp(123), + /Expected non-empty string for xmp but received 123 of type number/ + ); + }); + + it('withXmp XMP validation - null input', () => { + assert.throws( + () => sharp().withXmp(null), + /Expected non-empty string for xmp but received null of type object/ + ); + }); + + it('withXmp XMP validation - empty string', () => { + assert.throws( + () => sharp().withXmp(''), + /Expected non-empty string for xmp/ + ); + }); + }); + + describe('Invalid parameters', () => { + it('String orientation', () => { + assert.throws(() => { sharp().withMetadata({ orientation: 'zoinks' }); }); }); - it('Negative orientation', function () { - assert.throws(function () { + it('Negative orientation', () => { + assert.throws(() => { sharp().withMetadata({ orientation: -1 }); }); }); - it('Zero orientation', function () { - assert.throws(function () { + it('Zero orientation', () => { + assert.throws(() => { sharp().withMetadata({ orientation: 0 }); }); }); - it('Too large orientation', function () { - assert.throws(function () { + it('Too large orientation', () => { + assert.throws(() => { sharp().withMetadata({ orientation: 9 }); }); }); - it('Non-numeric density', function () { - assert.throws(function () { + it('Non-numeric density', () => { + assert.throws(() => { sharp().withMetadata({ density: '1' }); }); }); - it('Negative density', function () { - assert.throws(function () { + it('Negative density', () => { + assert.throws(() => { sharp().withMetadata({ density: -1 }); }); }); - it('Non string icc', function () { - assert.throws(function () { + it('Non string icc', () => { + assert.throws(() => { sharp().withMetadata({ icc: true }); }); }); - it('Non object exif', function () { - assert.throws(function () { + it('Non object exif', () => { + assert.throws(() => { sharp().withMetadata({ exif: false }); }); }); - it('Non string value in object exif', function () { - assert.throws(function () { + it('Non string value in object exif', () => { + assert.throws(() => { sharp().withMetadata({ exif: { ifd0: false } }); }); }); - it('Non string value in nested object exif', function () { - assert.throws(function () { + it('Non string value in nested object exif', () => { + assert.throws(() => { sharp().withMetadata({ exif: { ifd0: { fail: false } } }); }); }); diff --git a/test/unit/modulate.js b/test/unit/modulate.js index 988fc0554..40c5b83f1 100644 --- a/test/unit/modulate.js +++ b/test/unit/modulate.js @@ -1,14 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ const sharp = require('../../'); -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const fixtures = require('../fixtures'); -describe('Modulate', function () { - describe('Invalid options', function () { +describe('Modulate', () => { + describe('Invalid options', () => { [ null, undefined, @@ -24,9 +25,9 @@ describe('Modulate', function () { { hue: null }, { lightness: '+50' }, { lightness: null } - ].forEach(function (options) { - it('should throw', function () { - assert.throws(function () { + ].forEach((options) => { + it('should throw', () => { + assert.throws(() => { sharp(fixtures.inputJpg).modulate(options); }); }); diff --git a/test/unit/negate.js b/test/unit/negate.js index 646b9b13f..87196a592 100644 --- a/test/unit/negate.js +++ b/test/unit/negate.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Negate', function () { - it('negate (jpeg)', function (done) { +describe('Negate', () => { + it('negate (jpeg)', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -22,11 +23,11 @@ describe('Negate', function () { }); }); - it('negate (png)', function (done) { + it('negate (png)', (_t, done) => { sharp(fixtures.inputPng) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -35,11 +36,11 @@ describe('Negate', function () { }); }); - it('negate (png, trans)', function (done) { + it('negate (png, trans)', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -48,11 +49,11 @@ describe('Negate', function () { }); }); - it('negate (png, alpha)', function (done) { + it('negate (png, alpha)', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -61,11 +62,11 @@ describe('Negate', function () { }); }); - it('negate (webp)', function (done) { + it('negate (webp)', (_t, done) => { sharp(fixtures.inputWebP) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(320, info.width); @@ -74,11 +75,11 @@ describe('Negate', function () { }); }); - it('negate (webp, trans)', function (done) { + it('negate (webp, trans)', (_t, done) => { sharp(fixtures.inputWebPWithTransparency) .resize(320, 240) .negate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(320, info.width); @@ -87,11 +88,11 @@ describe('Negate', function () { }); }); - it('negate (true)', function (done) { + it('negate (true)', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .negate(true) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -100,22 +101,22 @@ describe('Negate', function () { }); }); - it('negate (false)', function (done) { + it('negate (false)', (_t, done) => { const output = fixtures.path('output.unmodified-by-negate.png'); sharp(fixtures.inputJpgWithLowContrast) .negate(false) - .toFile(output, function (err, info) { + .toFile(output, (err) => { if (err) throw err; fixtures.assertMaxColourDistance(output, fixtures.inputJpgWithLowContrast, 0); done(); }); }); - it('negate ({alpha: true})', function (done) { + it('negate ({alpha: true})', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .negate({ alpha: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -124,11 +125,11 @@ describe('Negate', function () { }); }); - it('negate non-alpha channels (png)', function (done) { + it('negate non-alpha channels (png)', (_t, done) => { sharp(fixtures.inputPng) .resize(320, 240) .negate({ alpha: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -137,11 +138,11 @@ describe('Negate', function () { }); }); - it('negate non-alpha channels (png, trans)', function (done) { + it('negate non-alpha channels (png, trans)', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 240) .negate({ alpha: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -150,11 +151,11 @@ describe('Negate', function () { }); }); - it('negate non-alpha channels (png, alpha)', function (done) { + it('negate non-alpha channels (png, alpha)', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .resize(320, 240) .negate({ alpha: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -163,11 +164,11 @@ describe('Negate', function () { }); }); - it('negate non-alpha channels (webp)', function (done) { + it('negate non-alpha channels (webp)', (_t, done) => { sharp(fixtures.inputWebP) .resize(320, 240) .negate({ alpha: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(320, info.width); @@ -176,11 +177,11 @@ describe('Negate', function () { }); }); - it('negate non-alpha channels (webp, trans)', function (done) { + it('negate non-alpha channels (webp, trans)', (_t, done) => { sharp(fixtures.inputWebPWithTransparency) .resize(320, 240) .negate({ alpha: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(320, info.width); @@ -205,8 +206,8 @@ describe('Negate', function () { assert.deepStrictEqual({ r, g, b }, { r: 245, g: 235, b: 225 }); }); - it('invalid alpha value', function () { - assert.throws(function () { + it('invalid alpha value', () => { + assert.throws(() => { sharp(fixtures.inputWebPWithTransparency).negate({ alpha: 'non-bool' }); }); }); diff --git a/test/unit/noise.js b/test/unit/noise.js index bdd6bbbca..c95006b6d 100644 --- a/test/unit/noise.js +++ b/test/unit/noise.js @@ -1,15 +1,16 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Gaussian noise', function () { - it('generate single-channel gaussian noise', function (done) { +describe('Gaussian noise', () => { + it('generate single-channel gaussian noise', (_t, done) => { const output = fixtures.path('output.noise-1-channel.png'); const noise = sharp({ create: { @@ -23,13 +24,13 @@ describe('Gaussian noise', function () { } } }).toColourspace('b-w'); - noise.toFile(output, function (err, info) { + noise.toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(1024, info.width); assert.strictEqual(768, info.height); assert.strictEqual(1, info.channels); - sharp(output).metadata(function (err, metadata) { + sharp(output).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('b-w', metadata.space); assert.strictEqual('uchar', metadata.depth); @@ -38,7 +39,7 @@ describe('Gaussian noise', function () { }); }); - it('generate 3-channels gaussian noise', function (done) { + it('generate 3-channels gaussian noise', (_t, done) => { const output = fixtures.path('output.noise-3-channels.png'); const noise = sharp({ create: { @@ -52,13 +53,13 @@ describe('Gaussian noise', function () { } } }); - noise.toFile(output, function (err, info) { + noise.toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(1024, info.width); assert.strictEqual(768, info.height); assert.strictEqual(3, info.channels); - sharp(output).metadata(function (err, metadata) { + sharp(output).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('srgb', metadata.space); assert.strictEqual('uchar', metadata.depth); @@ -67,7 +68,7 @@ describe('Gaussian noise', function () { }); }); - it('overlay 3-channels gaussian noise over image', function (done) { + it('overlay 3-channels gaussian noise over image', (_t, done) => { const output = fixtures.path('output.noise-image.jpg'); const noise = sharp({ create: { @@ -81,7 +82,7 @@ describe('Gaussian noise', function () { } } }); - noise.toBuffer(function (err, data, info) { + noise.toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(3, info.channels); sharp(fixtures.inputJpg) @@ -97,14 +98,14 @@ describe('Gaussian noise', function () { } } ]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); assert.strictEqual(3, info.channels); // perceptual hashing detects that images are the same (difference is <=1%) - fixtures.assertSimilar(output, fixtures.inputJpg, function (err) { + fixtures.assertSimilar(output, fixtures.inputJpg, (err) => { if (err) throw err; done(); }); @@ -112,7 +113,7 @@ describe('Gaussian noise', function () { }); }); - it('overlay strong single-channel (sRGB) gaussian noise with 25% transparency over transparent png image', function (done) { + it('overlay strong single-channel (sRGB) gaussian noise with 25% transparency over transparent png image', (_t, done) => { const output = fixtures.path('output.noise-image-transparent.png'); const width = 320; const height = 240; @@ -135,14 +136,14 @@ describe('Gaussian noise', function () { }); noise .toColourspace('b-w') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(1, info.channels); sharp(data, { raw: rawData }) .joinChannel(data, { raw: rawData }) // r channel .joinChannel(data, { raw: rawData }) // b channel .joinChannel(Buffer.alloc(width * height, 64), { raw: rawData }) // alpha channel - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(4, info.channels); sharp(fixtures.inputPngRGBWithAlpha) @@ -158,13 +159,13 @@ describe('Gaussian noise', function () { } } ]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(width, info.width); assert.strictEqual(height, info.height); assert.strictEqual(4, info.channels); - fixtures.assertSimilar(output, fixtures.inputPngRGBWithAlpha, { threshold: 10 }, function (err) { + fixtures.assertSimilar(output, fixtures.inputPngRGBWithAlpha, { threshold: 10 }, (err) => { if (err) throw err; done(); }); @@ -173,16 +174,36 @@ describe('Gaussian noise', function () { }); }); - it('no create object properties specified', function () { - assert.throws(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', () => { + assert.throws(() => { sharp({ create: {} }); }); }); - it('invalid noise object', function () { - assert.throws(function () { + it('invalid noise object', () => { + assert.throws(() => { sharp({ create: { width: 100, @@ -194,8 +215,8 @@ describe('Gaussian noise', function () { }); }); - it('unknown type of noise', function () { - assert.throws(function () { + it('unknown type of noise', () => { + assert.throws(() => { sharp({ create: { width: 100, @@ -209,8 +230,8 @@ describe('Gaussian noise', function () { }); }); - it('gaussian noise, invalid amount of channels', function () { - assert.throws(function () { + it('gaussian noise, invalid amount of channels', () => { + assert.throws(() => { sharp({ create: { width: 100, @@ -226,8 +247,8 @@ describe('Gaussian noise', function () { }); }); - it('gaussian noise, invalid mean', function () { - assert.throws(function () { + it('gaussian noise, invalid mean', () => { + assert.throws(() => { sharp({ create: { width: 100, @@ -243,8 +264,8 @@ describe('Gaussian noise', function () { }); }); - it('gaussian noise, invalid sigma', function () { - assert.throws(function () { + it('gaussian noise, invalid sigma', () => { + assert.throws(() => { sharp({ create: { width: 100, @@ -259,4 +280,29 @@ 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/ + ); + }); }); diff --git a/test/unit/normalize.js b/test/unit/normalize.js index d9e62d826..2c0cf32c6 100644 --- a/test/unit/normalize.js +++ b/test/unit/normalize.js @@ -1,14 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -const assertNormalized = function (data) { +const assertNormalized = (data) => { let min = 255; let max = 0; for (let i = 0; i < data.length; i++) { @@ -19,48 +20,48 @@ const assertNormalized = function (data) { assert.ok(max > 248, 'max too low'); }; -describe('Normalization', function () { - it('spreads rgb image values between 0 and 255', function (done) { +describe('Normalization', () => { + it('spreads rgb image values between 0 and 255', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .normalise() .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; assertNormalized(data); done(); }); }); - it('spreads grayscaled image values between 0 and 255', function (done) { + it('spreads grayscaled image values between 0 and 255', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .greyscale() .normalize() .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; assertNormalized(data); done(); }); }); - it('stretches greyscale images with alpha channel', function (done) { + it('stretches greyscale images with alpha channel', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .normalise() .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; assertNormalized(data); done(); }); }); - it('keeps an existing alpha channel', function (done) { + it('keeps an existing alpha channel', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(8, 8) .normalize() - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) return done(err); assert.strictEqual(4, metadata.channels); assert.strictEqual(true, metadata.hasAlpha); @@ -70,13 +71,13 @@ describe('Normalization', function () { }); }); - it('keeps the alpha channel of greyscale images intact', function (done) { + it('keeps the alpha channel of greyscale images intact', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .resize(8, 8) .normalise() - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) return done(err); assert.strictEqual(true, metadata.hasAlpha); assert.strictEqual(4, metadata.channels); @@ -86,76 +87,76 @@ describe('Normalization', function () { }); }); - it('does not alter images with only one color', function (done) { + it('does not alter images with only one color', (_t, done) => { const output = fixtures.path('output.unmodified-png-with-one-color.png'); sharp(fixtures.inputPngWithOneColor) .normalize() - .toFile(output, function (err, info) { + .toFile(output, (err) => { if (err) done(err); fixtures.assertMaxColourDistance(output, fixtures.inputPngWithOneColor, 0); done(); }); }); - it('works with 16-bit RGBA images', function (done) { + it('works with 16-bit RGBA images', (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .normalise() .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; assertNormalized(data); done(); }); }); - it('should handle luminance range', function (done) { + it('should handle luminance range', (_t, done) => { sharp(fixtures.inputJpgWithLowContrast) .normalise({ lower: 10, upper: 70 }) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; assertNormalized(data); done(); }); }); - it('should allow lower without upper', function () { + it('should allow lower without upper', () => { assert.doesNotThrow(() => sharp().normalize({ lower: 2 })); }); - it('should allow upper without lower', function () { + it('should allow upper without lower', () => { assert.doesNotThrow(() => sharp().normalize({ upper: 98 })); }); - it('should throw when lower is out of range', function () { + it('should throw when lower is out of range', () => { assert.throws( () => sharp().normalise({ lower: -10 }), /Expected number between 0 and 99 for lower but received -10 of type number/ ); }); - it('should throw when upper is out of range', function () { + it('should throw when upper is out of range', () => { assert.throws( () => sharp().normalise({ upper: 110 }), /Expected number between 1 and 100 for upper but received 110 of type number/ ); }); - it('should throw when lower is not a number', function () { + it('should throw when lower is not a number', () => { assert.throws( () => sharp().normalise({ lower: 'fail' }), /Expected number between 0 and 99 for lower but received fail of type string/ ); }); - it('should throw when upper is not a number', function () { + it('should throw when upper is not a number', () => { assert.throws( () => sharp().normalise({ upper: 'fail' }), /Expected number between 1 and 100 for upper but received fail of type string/ ); }); - it('should throw when the lower and upper are equal', function () { + it('should throw when the lower and upper are equal', () => { assert.throws( () => sharp().normalise({ lower: 2, upper: 2 }), /Expected lower to be less than upper for range but received 2 >= 2/ ); }); - it('should throw when the lower is greater than upper', function () { + it('should throw when the lower is greater than upper', () => { assert.throws( () => sharp().normalise({ lower: 3, upper: 2 }), /Expected lower to be less than upper for range but received 3 >= 2/ diff --git a/test/unit/png.js b/test/unit/png.js index 84a74cbc6..81800e1a7 100644 --- a/test/unit/png.js +++ b/test/unit/png.js @@ -1,33 +1,34 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('PNG', function () { - it('compression level is valid', function () { - assert.doesNotThrow(function () { +describe('PNG', () => { + it('compression level is valid', () => { + assert.doesNotThrow(() => { sharp().png({ compressionLevel: 0 }); }); }); - it('compression level is invalid', function () { - assert.throws(function () { + it('compression level is invalid', () => { + assert.throws(() => { sharp().png({ compressionLevel: -1 }); }); }); - it('default compressionLevel generates smaller file than compressionLevel=0', function (done) { + it('default compressionLevel generates smaller file than compressionLevel=0', (_t, done) => { // First generate with default compressionLevel sharp(fixtures.inputPng) .resize(320, 240) .png() - .toBuffer(function (err, defaultData, defaultInfo) { + .toBuffer((err, defaultData, defaultInfo) => { if (err) throw err; assert.strictEqual(true, defaultData.length > 0); assert.strictEqual('png', defaultInfo.format); @@ -35,7 +36,7 @@ describe('PNG', function () { sharp(fixtures.inputPng) .resize(320, 240) .png({ compressionLevel: 0 }) - .toBuffer(function (err, largerData, largerInfo) { + .toBuffer((err, largerData, largerInfo) => { if (err) throw err; assert.strictEqual(true, largerData.length > 0); assert.strictEqual('png', largerInfo.format); @@ -45,12 +46,12 @@ describe('PNG', function () { }); }); - it('without adaptiveFiltering generates smaller file', function (done) { + it('without adaptiveFiltering generates smaller file', (_t, done) => { // First generate with adaptive filtering sharp(fixtures.inputPng) .resize(320, 240) .png({ adaptiveFiltering: true }) - .toBuffer(function (err, adaptiveData, adaptiveInfo) { + .toBuffer((err, adaptiveData, adaptiveInfo) => { if (err) throw err; assert.strictEqual(true, adaptiveData.length > 0); assert.strictEqual(adaptiveData.length, adaptiveInfo.size); @@ -61,7 +62,7 @@ describe('PNG', function () { sharp(fixtures.inputPng) .resize(320, 240) .png({ adaptiveFiltering: false }) - .toBuffer(function (err, withoutAdaptiveData, withoutAdaptiveInfo) { + .toBuffer((err, withoutAdaptiveData, withoutAdaptiveInfo) => { if (err) throw err; assert.strictEqual(true, withoutAdaptiveData.length > 0); assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size); @@ -74,17 +75,17 @@ describe('PNG', function () { }); }); - it('Invalid PNG adaptiveFiltering value throws error', function () { - assert.throws(function () { + it('Invalid PNG adaptiveFiltering value throws error', () => { + assert.throws(() => { sharp().png({ adaptiveFiltering: 1 }); }); }); - it('Progressive PNG image', function (done) { + it('Progressive PNG image', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .png({ progressive: false }) - .toBuffer(function (err, nonProgressiveData, nonProgressiveInfo) { + .toBuffer((err, nonProgressiveData, nonProgressiveInfo) => { if (err) throw err; assert.strictEqual(true, nonProgressiveData.length > 0); assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size); @@ -93,7 +94,7 @@ describe('PNG', function () { assert.strictEqual(240, nonProgressiveInfo.height); sharp(nonProgressiveData) .png({ progressive: true }) - .toBuffer(function (err, progressiveData, progressiveInfo) { + .toBuffer((err, progressiveData, progressiveInfo) => { if (err) throw err; assert.strictEqual(true, progressiveData.length > 0); assert.strictEqual(progressiveData.length, progressiveInfo.size); @@ -106,11 +107,11 @@ describe('PNG', function () { }); }); - it('16-bit grey+alpha PNG identity transform', function () { + it('16-bit grey+alpha PNG identity transform', () => { const actual = fixtures.path('output.16-bit-grey-alpha-identity.png'); return sharp(fixtures.inputPng16BitGreyAlpha) .toFile(actual) - .then(function () { + .then(() => { fixtures.assertMaxColourDistance(actual, fixtures.expected('16-bit-grey-alpha-identity.png')); }); }); @@ -137,6 +138,7 @@ describe('PNG', function () { .toBuffer(); const { size, ...metadata } = await sharp(data).metadata(); + void size; assert.deepStrictEqual(metadata, { autoOrient: { height: 68, @@ -158,30 +160,30 @@ describe('PNG', function () { }); }); - it('Valid PNG libimagequant palette value does not throw error', function () { - assert.doesNotThrow(function () { + it('Valid PNG libimagequant palette value does not throw error', () => { + assert.doesNotThrow(() => { sharp().png({ palette: false }); }); }); - it('Invalid PNG libimagequant palette value throws error', function () { - assert.throws(function () { + it('Invalid PNG libimagequant palette value throws error', () => { + assert.throws(() => { sharp().png({ palette: 'fail' }); }); }); - it('Valid PNG libimagequant quality value produces image of same size or smaller', function () { + it('Valid PNG libimagequant quality value produces image of same size or smaller', () => { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 80 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 100 }).toBuffer() - ]).then(function (data) { + ]).then((data) => { assert.strictEqual(true, data[0].length <= data[1].length); }); }); - it('Invalid PNG libimagequant quality value throws error', function () { - assert.throws(function () { + it('Invalid PNG libimagequant quality value throws error', () => { + assert.throws(() => { sharp().png({ quality: 101 }); }); }); @@ -192,24 +194,24 @@ describe('PNG', function () { }); }); - it('Valid PNG libimagequant colours value produces image of same size or smaller', function () { + it('Valid PNG libimagequant colours value produces image of same size or smaller', () => { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ colours: 100 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ colours: 200 }).toBuffer() - ]).then(function (data) { + ]).then((data) => { assert.strictEqual(true, data[0].length <= data[1].length); }); }); - it('Invalid PNG libimagequant colours value throws error', function () { - assert.throws(function () { + it('Invalid PNG libimagequant colours value throws error', () => { + assert.throws(() => { sharp().png({ colours: -1 }); }); }); - it('Invalid PNG libimagequant colors value throws error', function () { - assert.throws(function () { + it('Invalid PNG libimagequant colors value throws error', () => { + assert.throws(() => { sharp().png({ colors: 0.1 }); }); }); @@ -233,18 +235,18 @@ describe('PNG', function () { assert.strictEqual(space, 'b-w'); }); - it('Valid PNG libimagequant dither value produces image of same size or smaller', function () { + it('Valid PNG libimagequant dither value produces image of same size or smaller', () => { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ dither: 0.1 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ dither: 0.9 }).toBuffer() - ]).then(function (data) { + ]).then((data) => { assert.strictEqual(true, data[0].length <= data[1].length); }); }); - it('Invalid PNG libimagequant dither value throws error', function () { - assert.throws(function () { + it('Invalid PNG libimagequant dither value throws error', () => { + assert.throws(() => { sharp().png({ dither: 'fail' }); }); }); diff --git a/test/unit/raw.js b/test/unit/raw.js index 7ad240121..a3e9d563d 100644 --- a/test/unit/raw.js +++ b/test/unit/raw.js @@ -1,66 +1,96 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Raw pixel data', function () { - describe('Raw pixel input', function () { - it('Empty data', function () { - assert.throws(function () { +describe('Raw pixel data', () => { + describe('Raw pixel input', () => { + it('Empty data', () => { + assert.throws(() => { sharp(Buffer.from('')); }, /empty/); - assert.throws(function () { + assert.throws(() => { sharp(new ArrayBuffer(0)); }, /empty/); - assert.throws(function () { + assert.throws(() => { sharp(new Uint8Array(0)); }, /empty/); - assert.throws(function () { + assert.throws(() => { sharp(new Uint8ClampedArray(0)); }, /empty/); }); - it('Missing options', function () { - assert.throws(function () { + it('Missing options', () => { + assert.throws(() => { sharp({ raw: {} }); }); }); - it('Incomplete options', function () { - assert.throws(function () { + it('Incomplete options', () => { + assert.throws(() => { sharp({ raw: { width: 1, height: 1 } }); }); }); - it('Invalid channels', function () { - assert.throws(function () { + it('Invalid channels', () => { + assert.throws(() => { sharp({ raw: { width: 1, height: 1, channels: 5 } }); }); }); - it('Invalid height', function () { - assert.throws(function () { + it('Invalid height', () => { + assert.throws(() => { sharp({ raw: { width: 1, height: 0, channels: 4 } }); }); }); - it('Invalid width', function () { - assert.throws(function () { + it('Invalid width', () => { + assert.throws(() => { sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } }); }); }); - it('RGB', function (done) { + 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', (_t, done) => { // Convert to raw pixel data sharp(fixtures.inputJpg) .resize(256) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(209, info.height); @@ -74,7 +104,7 @@ describe('Raw pixel data', function () { } }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(209, info.height); @@ -84,12 +114,12 @@ describe('Raw pixel data', function () { }); }); - it('RGBA', function (done) { + it('RGBA', (_t, done) => { // Convert to raw pixel data sharp(fixtures.inputPngOverlayLayer1) .resize(256) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(192, info.height); @@ -103,7 +133,7 @@ describe('Raw pixel data', function () { } }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(192, info.height); @@ -113,12 +143,12 @@ describe('Raw pixel data', function () { }); }); - it('RGBA premultiplied', function (done) { + it('RGBA premultiplied', (_t, done) => { // Convert to raw pixel data sharp(fixtures.inputPngSolidAlpha) .resize(256) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(192, info.height); @@ -148,7 +178,7 @@ describe('Raw pixel data', function () { } }) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(256, info.width); assert.strictEqual(192, info.height); @@ -159,7 +189,7 @@ describe('Raw pixel data', function () { }); }); - it('JPEG to raw Stream and back again', function (done) { + it('JPEG to raw Stream and back again', (_t, done) => { const width = 32; const height = 24; const writable = sharp({ @@ -171,7 +201,7 @@ describe('Raw pixel data', function () { }); writable .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(32, info.width); @@ -185,13 +215,13 @@ describe('Raw pixel data', function () { }); }); - describe('Output raw, uncompressed image data', function () { - it('1 channel greyscale image', function (done) { + describe('Output raw, uncompressed image data', () => { + it('1 channel greyscale image', (_t, done) => { sharp(fixtures.inputJpg) .greyscale() .resize(32, 24) .raw() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(32 * 24 * 1, info.size); assert.strictEqual(data.length, info.size); @@ -203,11 +233,11 @@ describe('Raw pixel data', function () { }); }); - it('3 channel colour image without transparency', function (done) { + it('3 channel colour image without transparency', (_t, done) => { sharp(fixtures.inputJpg) .resize(32, 24) .toFormat('raw') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(32 * 24 * 3, info.size); assert.strictEqual(data.length, info.size); @@ -218,11 +248,11 @@ describe('Raw pixel data', function () { }); }); - it('4 channel colour image with transparency', function (done) { + it('4 channel colour image with transparency', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(32, 24) .toFormat(sharp.format.raw) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(32 * 24 * 4, info.size); assert.strictEqual(data.length, info.size); @@ -248,28 +278,28 @@ describe('Raw pixel data', function () { ); }); - describe('Raw pixel depths', function () { - it('Invalid depth', function () { - assert.throws(function () { + describe('Raw pixel depths', () => { + it('Invalid depth', () => { + assert.throws(() => { sharp(Buffer.alloc(3), { raw: { width: 1, height: 1, channels: 3 } }) .raw({ depth: 'zoinks' }); }); }); - for (const { constructor, depth, bits } of [ - { constructor: Uint8Array, depth: undefined, bits: 8 }, - { constructor: Uint8Array, depth: 'uchar', bits: 8 }, - { constructor: Uint8ClampedArray, depth: 'uchar', bits: 8 }, - { constructor: Int8Array, depth: 'char', bits: 8 }, - { constructor: Uint16Array, depth: 'ushort', bits: 16 }, - { constructor: Int16Array, depth: 'short', bits: 16 }, - { constructor: Uint32Array, depth: 'uint', bits: 32 }, - { constructor: Int32Array, depth: 'int', bits: 32 }, - { constructor: Float32Array, depth: 'float', bits: 32 }, - { constructor: Float64Array, depth: 'double', bits: 64 } + for (const { type, depth, bits } of [ + { type: Uint8Array, depth: undefined, bits: 8 }, + { type: Uint8Array, depth: 'uchar', bits: 8 }, + { type: Uint8ClampedArray, depth: 'uchar', bits: 8 }, + { type: Int8Array, depth: 'char', bits: 8 }, + { type: Uint16Array, depth: 'ushort', bits: 16 }, + { type: Int16Array, depth: 'short', bits: 16 }, + { type: Uint32Array, depth: 'uint', bits: 32 }, + { type: Int32Array, depth: 'int', bits: 32 }, + { type: Float32Array, depth: 'float', bits: 32 }, + { type: Float64Array, depth: 'double', bits: 64 } ]) { - it(constructor.name, () => - sharp(new constructor(3), { raw: { width: 1, height: 1, channels: 3 } }) + it(type.name, () => + sharp(new type(3), { raw: { width: 1, height: 1, channels: 3 } }) .raw({ depth }) .toBuffer({ resolveWithObject: true }) .then(({ data, info }) => { @@ -285,6 +315,21 @@ 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(); + + 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', () => { it('grey', async () => { const grey = 42000; diff --git a/test/unit/recomb.js b/test/unit/recomb.js index 499597204..ec05eb502 100644 --- a/test/unit/recomb.js +++ b/test/unit/recomb.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); @@ -14,12 +15,12 @@ const sepia = [ [0.2392, 0.4696, 0.0912] ]; -describe('Recomb', function () { - it('applies a sepia filter using recomb', function (done) { +describe('Recomb', () => { + it('applies a sepia filter using recomb', (_t, done) => { const output = fixtures.path('output.recomb-sepia.jpg'); sharp(fixtures.inputJpgWithLandscapeExif1) .recomb(sepia) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(600, info.width); @@ -33,11 +34,11 @@ describe('Recomb', function () { }); }); - it('applies a sepia filter using recomb to an PNG with Alpha', function (done) { + it('applies a sepia filter using recomb to an PNG with Alpha', (_t, done) => { const output = fixtures.path('output.recomb-sepia.png'); sharp(fixtures.inputPngAlphaPremultiplicationSmall) .recomb(sepia) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(1024, info.width); @@ -65,7 +66,7 @@ describe('Recomb', function () { assert.strictEqual(3, info.channels); }); - it('applies a different sepia filter using recomb', function (done) { + it('applies a different sepia filter using recomb', (_t, done) => { const output = fixtures.path('output.recomb-sepia2.jpg'); sharp(fixtures.inputJpgWithLandscapeExif1) .recomb([ @@ -73,7 +74,7 @@ describe('Recomb', function () { [0.349, 0.686, 0.168], [0.272, 0.534, 0.131] ]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(600, info.width); @@ -86,7 +87,7 @@ describe('Recomb', function () { done(); }); }); - it('increases the saturation of the image', function (done) { + it('increases the saturation of the image', (_t, done) => { const saturationLevel = 1; const output = fixtures.path('output.recomb-saturation.jpg'); sharp(fixtures.inputJpgWithLandscapeExif1) @@ -107,7 +108,7 @@ describe('Recomb', function () { saturationLevel + 1 - 0.114 ] ]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(600, info.width); @@ -121,7 +122,7 @@ describe('Recomb', function () { }); }); - it('applies opacity 30% to the image', function (done) { + it('applies opacity 30% to the image', (_t, done) => { const output = fixtures.path('output.recomb-opacity.png'); sharp(fixtures.inputPngWithTransparent) .recomb([ @@ -130,7 +131,7 @@ describe('Recomb', function () { [0, 0, 1, 0], [0, 0, 0, 0.3] ]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(48, info.width); @@ -144,19 +145,19 @@ describe('Recomb', function () { }); }); - describe('invalid matrix specification', function () { - it('missing', function () { - assert.throws(function () { + describe('invalid matrix specification', () => { + it('missing', () => { + assert.throws(() => { sharp(fixtures.inputJpg).recomb(); }); }); - it('incorrect flat data', function () { - assert.throws(function () { + it('incorrect flat data', () => { + assert.throws(() => { sharp(fixtures.inputJpg).recomb([1, 2, 3, 4, 5, 6, 7, 8, 9]); }); }); - it('incorrect sub size', function () { - assert.throws(function () { + it('incorrect sub size', () => { + assert.throws(() => { sharp(fixtures.inputJpg).recomb([ [1, 2, 3, 4], [5, 6, 7, 8], @@ -164,8 +165,8 @@ describe('Recomb', function () { ]); }); }); - it('incorrect top size', function () { - assert.throws(function () { + it('incorrect top size', () => { + assert.throws(() => { sharp(fixtures.inputJpg).recomb([[1, 2, 3, 4], [5, 6, 7, 8]]); }); }); diff --git a/test/unit/resize-contain.js b/test/unit/resize-contain.js index 04c70df17..36cc4518e 100644 --- a/test/unit/resize-contain.js +++ b/test/unit/resize-contain.js @@ -1,22 +1,23 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Resize fit=contain', function () { - it('Allows specifying the position as a string', function (done) { +describe('Resize fit=contain', () => { + it('Allows specifying the position as a string', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240, { fit: 'contain', position: 'center' }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -24,11 +25,11 @@ describe('Resize fit=contain', function () { }); }); - it('JPEG within PNG, no alpha channel', function (done) { + it('JPEG within PNG, no alpha channel', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240, { fit: 'contain' }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -39,14 +40,14 @@ describe('Resize fit=contain', function () { }); }); - it('JPEG within WebP, to include alpha channel', function (done) { + it('JPEG within WebP, to include alpha channel', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) .webp() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -57,10 +58,10 @@ describe('Resize fit=contain', function () { }); }); - it('PNG with alpha channel', function (done) { + it('PNG with alpha channel', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(50, 50, { fit: 'contain' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -71,10 +72,10 @@ describe('Resize fit=contain', function () { }); }); - it('16-bit PNG with alpha channel', function (done) { + it('16-bit PNG with alpha channel', (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .resize(32, 16, { fit: 'contain' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -85,13 +86,13 @@ describe('Resize fit=contain', function () { }); }); - it('16-bit PNG with alpha channel onto RGBA', function (done) { + it('16-bit PNG with alpha channel onto RGBA', (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .resize(32, 16, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -102,13 +103,13 @@ describe('Resize fit=contain', function () { }); }); - it('PNG with 2 channels', function (done) { + it('PNG with 2 channels', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .resize(32, 16, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -119,14 +120,14 @@ describe('Resize fit=contain', function () { }); }); - it('TIFF in LAB colourspace onto RGBA background', function (done) { + it('TIFF in LAB colourspace onto RGBA background', (_t, done) => { sharp(fixtures.inputTiffCielab) .resize(64, 128, { fit: 'contain', background: { r: 255, g: 102, b: 0, alpha: 0.5 } }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -137,10 +138,10 @@ describe('Resize fit=contain', function () { }); }); - it('Enlarge', function (done) { + it('Enlarge', (_t, done) => { sharp(fixtures.inputPngWithOneColor) .resize(320, 240, { fit: 'contain' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -151,14 +152,14 @@ describe('Resize fit=contain', function () { }); }); - describe('Animated WebP', function () { - it('Width only', function (done) { + describe('Animated WebP', () => { + it('Width only', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(320, 240, { fit: 'contain', background: { r: 255, g: 0, b: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -169,13 +170,13 @@ describe('Resize fit=contain', function () { }); }); - it('Height only', function (done) { + it('Height only', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(240, 320, { fit: 'contain', background: { r: 255, g: 0, b: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -187,22 +188,22 @@ describe('Resize fit=contain', function () { }); }); - it('Invalid position values should fail', function () { - [-1, 8.1, 9, 1000000, false, 'vallejo'].forEach(function (position) { - assert.throws(function () { + it('Invalid position values should fail', () => { + [-1, 8.1, 9, 1000000, false, 'vallejo'].forEach((position) => { + assert.throws(() => { sharp().resize(null, null, { fit: 'contain', position }); }); }); }); - it('Position horizontal top', function (done) { + it('Position horizontal top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -213,14 +214,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal right top', function (done) { + it('Position horizontal right top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -231,14 +232,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal right', function (done) { + it('Position horizontal right', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -249,14 +250,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal right bottom', function (done) { + it('Position horizontal right bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -267,14 +268,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal bottom', function (done) { + it('Position horizontal bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -285,14 +286,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal left bottom', function (done) { + it('Position horizontal left bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -303,14 +304,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal left', function (done) { + it('Position horizontal left', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -321,14 +322,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal left top', function (done) { + it('Position horizontal left top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -339,14 +340,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal north', function (done) { + it('Position horizontal north', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.north }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -357,14 +358,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal northeast', function (done) { + it('Position horizontal northeast', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.northeast }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -375,14 +376,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal east', function (done) { + it('Position horizontal east', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.east }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -393,14 +394,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal southeast', function (done) { + it('Position horizontal southeast', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.southeast }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -411,14 +412,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal south', function (done) { + it('Position horizontal south', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.south }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -429,14 +430,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal southwest', function (done) { + it('Position horizontal southwest', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.southwest }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -447,14 +448,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal west', function (done) { + it('Position horizontal west', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.west }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -465,14 +466,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal northwest', function (done) { + it('Position horizontal northwest', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.northwest }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -483,14 +484,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position horizontal center', function (done) { + it('Position horizontal center', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 100, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.center }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -501,14 +502,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical top', function (done) { + it('Position vertical top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -519,14 +520,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical right top', function (done) { + it('Position vertical right top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -537,14 +538,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical right', function (done) { + it('Position vertical right', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -555,14 +556,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical right bottom', function (done) { + it('Position vertical right bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'right bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -573,14 +574,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical bottom', function (done) { + it('Position vertical bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -591,14 +592,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical left bottom', function (done) { + it('Position vertical left bottom', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left bottom' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -609,14 +610,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical left', function (done) { + it('Position vertical left', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -627,14 +628,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical left top', function (done) { + it('Position vertical left top', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: 'left top' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -645,14 +646,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical north', function (done) { + it('Position vertical north', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.north }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -663,14 +664,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical northeast', function (done) { + it('Position vertical northeast', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.northeast }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -681,14 +682,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical east', function (done) { + it('Position vertical east', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.east }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -699,14 +700,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical southeast', function (done) { + it('Position vertical southeast', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.southeast }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -717,14 +718,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical south', function (done) { + it('Position vertical south', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.south }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -735,14 +736,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical southwest', function (done) { + it('Position vertical southwest', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.southwest }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -753,14 +754,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical west', function (done) { + it('Position vertical west', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.west }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -771,14 +772,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical northwest', function (done) { + it('Position vertical northwest', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.northwest }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -789,14 +790,14 @@ describe('Resize fit=contain', function () { }); }); - it('Position vertical center', function (done) { + it('Position vertical center', (_t, done) => { sharp(fixtures.inputPngEmbed) .resize(200, 200, { fit: sharp.fit.contain, background: { r: 0, g: 0, b: 0, alpha: 0 }, position: sharp.gravity.center }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -806,4 +807,33 @@ describe('Resize fit=contain', function () { 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); + }); }); diff --git a/test/unit/resize-cover.js b/test/unit/resize-cover.js index 95ce1b012..4e2dda737 100644 --- a/test/unit/resize-cover.js +++ b/test/unit/resize-cover.js @@ -1,14 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Resize fit=cover', function () { +describe('Resize fit=cover', () => { [ // Position { @@ -201,14 +202,14 @@ describe('Resize fit=cover', function () { gravity: sharp.gravity.northwest, fixture: 'gravity-west.jpg' } - ].forEach(function (settings) { - it(settings.name, function (done) { + ].forEach((settings) => { + it(settings.name, (_t, done) => { sharp(fixtures.inputJpg) .resize(settings.width, settings.height, { fit: sharp.fit.cover, position: settings.gravity }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(settings.width, info.width); assert.strictEqual(settings.height, info.height); @@ -217,13 +218,13 @@ describe('Resize fit=cover', function () { }); }); - it('Allows specifying the gravity as a string', function (done) { + it('Allows specifying the gravity as a string', (_t, done) => { sharp(fixtures.inputJpg) .resize(80, 320, { fit: sharp.fit.cover, position: 'east' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(80, info.width); assert.strictEqual(320, info.height); @@ -231,52 +232,48 @@ describe('Resize fit=cover', function () { }); }); - it('Invalid position values fail', function () { - assert.throws(function () { + it('Invalid position values fail', () => { + assert.throws(() => { sharp().resize(null, null, { fit: 'cover', position: 9 }); }, /Expected valid position\/gravity\/strategy for position but received 9 of type number/); - assert.throws(function () { + assert.throws(() => { sharp().resize(null, null, { fit: 'cover', position: 1.1 }); }, /Expected valid position\/gravity\/strategy for position but received 1.1 of type number/); - assert.throws(function () { + assert.throws(() => { sharp().resize(null, null, { fit: 'cover', position: -1 }); }, /Expected valid position\/gravity\/strategy for position but received -1 of type number/); - assert.throws(function () { + assert.throws(() => { sharp().resize(null, null, { fit: 'cover', position: 'zoinks' }).crop(); }, /Expected valid position\/gravity\/strategy for position but received zoinks of type string/); }); - it('Uses default value when none specified', function () { - assert.doesNotThrow(function () { + it('Uses default value when none specified', () => { + assert.doesNotThrow(() => { sharp().resize(null, null, { fit: 'cover' }); }); }); - it('Skip crop when post-resize dimensions are at target', function () { - return sharp(fixtures.inputJpg) + it('Skip crop when post-resize dimensions are at target', () => sharp(fixtures.inputJpg) .resize(1600, 1200) .toBuffer() - .then(function (input) { - return sharp(input) + .then((input) => sharp(input) .resize(1110, null, { fit: sharp.fit.cover, position: sharp.strategy.attention }) .toBuffer({ resolveWithObject: true }) - .then(function (result) { + .then((result) => { assert.strictEqual(1110, result.info.width); assert.strictEqual(832, result.info.height); assert.strictEqual(undefined, result.info.cropOffsetLeft); assert.strictEqual(undefined, result.info.cropOffsetTop); - }); - }); - }); + }))); - describe('Animated WebP', function () { - it('Width only', function (done) { + describe('Animated WebP', () => { + it('Width only', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(80, 320, { fit: sharp.fit.cover }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(80, info.width); assert.strictEqual(320 * 9, info.height); @@ -284,10 +281,10 @@ describe('Resize fit=cover', function () { }); }); - it('Height only', function (done) { + it('Height only', (_t, done) => { sharp(fixtures.inputWebPAnimated, { pages: -1 }) .resize(320, 80, { fit: sharp.fit.cover }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(80 * 9, info.height); @@ -296,14 +293,14 @@ describe('Resize fit=cover', function () { }); }); - describe('Entropy-based strategy', function () { - it('JPEG', function (done) { + describe('Entropy-based strategy', () => { + it('JPEG', (_t, done) => { sharp(fixtures.inputJpg) .resize(80, 320, { fit: 'cover', position: sharp.strategy.entropy }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(3, info.channels); @@ -315,13 +312,13 @@ describe('Resize fit=cover', function () { }); }); - it('PNG', function (done) { + it('PNG', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 80, { fit: 'cover', position: sharp.strategy.entropy }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); @@ -333,13 +330,13 @@ describe('Resize fit=cover', function () { }); }); - it('supports the strategy passed as a string', function (done) { + it('supports the strategy passed as a string', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 80, { fit: 'cover', position: 'entropy' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); @@ -364,14 +361,14 @@ describe('Resize fit=cover', function () { ); }); - describe('Attention strategy', function () { - it('JPEG', function (done) { + describe('Attention strategy', () => { + it('JPEG', (_t, done) => { sharp(fixtures.inputJpg) .resize(80, 320, { fit: 'cover', position: sharp.strategy.attention }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(3, info.channels); @@ -385,13 +382,13 @@ describe('Resize fit=cover', function () { }); }); - it('PNG', function (done) { + it('PNG', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 80, { fit: 'cover', position: sharp.strategy.attention }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); @@ -405,13 +402,13 @@ describe('Resize fit=cover', function () { }); }); - it('WebP', function (done) { + it('WebP', (_t, done) => { sharp(fixtures.inputWebP) .resize(320, 80, { fit: 'cover', position: sharp.strategy.attention }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(3, info.channels); @@ -425,13 +422,13 @@ describe('Resize fit=cover', function () { }); }); - it('supports the strategy passed as a string', function (done) { + it('supports the strategy passed as a string', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 80, { fit: 'cover', position: 'attention' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); diff --git a/test/unit/resize.js b/test/unit/resize.js index 255fb15ff..e0a001543 100644 --- a/test/unit/resize.js +++ b/test/unit/resize.js @@ -1,16 +1,17 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Resize dimensions', function () { - it('Exact crop', function (done) { - sharp(fixtures.inputJpg).resize(320, 240).toBuffer(function (err, data, info) { +describe('Resize dimensions', () => { + it('Exact crop', (_t, done) => { + sharp(fixtures.inputJpg).resize(320, 240).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -20,8 +21,8 @@ describe('Resize dimensions', function () { }); }); - it('Fixed width', function (done) { - sharp(fixtures.inputJpg).resize(320).toBuffer(function (err, data, info) { + it('Fixed width', (_t, done) => { + sharp(fixtures.inputJpg).resize(320).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -31,8 +32,8 @@ describe('Resize dimensions', function () { }); }); - it('Fixed height', function (done) { - sharp(fixtures.inputJpg).resize(null, 320).toBuffer(function (err, data, info) { + it('Fixed height', (_t, done) => { + sharp(fixtures.inputJpg).resize(null, 320).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -42,8 +43,8 @@ describe('Resize dimensions', function () { }); }); - it('Identity transform', function (done) { - sharp(fixtures.inputJpg).toBuffer(function (err, data, info) { + it('Identity transform', (_t, done) => { + sharp(fixtures.inputJpg).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -53,10 +54,10 @@ describe('Resize dimensions', function () { }); }); - it('Upscale', function (done) { + it('Upscale', (_t, done) => { sharp(fixtures.inputJpg) .resize(3000) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -66,26 +67,26 @@ describe('Resize dimensions', function () { }); }); - it('Invalid width - NaN', function () { - assert.throws(function () { + it('Invalid width - NaN', () => { + assert.throws(() => { sharp().resize('spoons', 240); }, /Expected positive integer for width but received spoons of type string/); }); - it('Invalid height - NaN', function () { - assert.throws(function () { + it('Invalid height - NaN', () => { + assert.throws(() => { sharp().resize(320, 'spoons'); }, /Expected positive integer for height but received spoons of type string/); }); - it('Invalid width - float', function () { - assert.throws(function () { + it('Invalid width - float', () => { + assert.throws(() => { sharp().resize(1.5, 240); }, /Expected positive integer for width but received 1.5 of type number/); }); - it('Invalid height - float', function () { - assert.throws(function () { + it('Invalid height - float', () => { + assert.throws(() => { sharp().resize(320, 1.5); }, /Expected positive integer for height but received 1.5 of type number/); }); @@ -102,34 +103,34 @@ describe('Resize dimensions', function () { }, /Expected positive integer for height but received 1.5 of type number/); }); - it('Invalid width - too large', function (done) { + it('Invalid width - too large', (_t, done) => { sharp(fixtures.inputJpg) .resize(0x4000, 1) .webp() - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, err instanceof Error); assert.strictEqual('Processed image is too large for the WebP format', err.message); done(); }); }); - it('Invalid height - too large', function (done) { + it('Invalid height - too large', (_t, done) => { sharp(fixtures.inputJpg) .resize(1, 0x4000) .webp() - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, err instanceof Error); assert.strictEqual('Processed image is too large for the WebP format', err.message); done(); }); }); - it('Webp resize then extract large image', function (done) { + it('Webp resize then extract large image', (_t, done) => { sharp(fixtures.inputWebP) .resize(0x4000, 0x4000) .extract({ top: 0x2000, left: 0x2000, width: 256, height: 256 }) .webp() - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(256, info.width); @@ -138,18 +139,18 @@ describe('Resize dimensions', function () { }); }); - it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) { + it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', (_t, done) => { sharp(fixtures.inputJpg) .resize(1080, 607) .webp() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(1080, info.width); assert.strictEqual(607, info.height); sharp(data) .resize(233, 131) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); assert.strictEqual(233, info.width); @@ -159,17 +160,17 @@ describe('Resize dimensions', function () { }); }); - it('JPEG shrink-on-load with 90 degree rotation, ensure recalculation is correct', function (done) { + it('JPEG shrink-on-load with 90 degree rotation, ensure recalculation is correct', (_t, done) => { sharp(fixtures.inputJpg) .resize(1920, 1280) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(1920, info.width); assert.strictEqual(1280, info.height); sharp(data) .rotate(90) .resize(533, 800) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(533, info.width); assert.strictEqual(800, info.height); @@ -178,11 +179,11 @@ describe('Resize dimensions', function () { }); }); - it('TIFF embed known to cause rounding errors', function (done) { + it('TIFF embed known to cause rounding errors', (_t, done) => { sharp(fixtures.inputTiff) .resize(240, 320, { fit: sharp.fit.contain }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -192,11 +193,11 @@ describe('Resize dimensions', function () { }); }); - it('TIFF known to cause rounding errors', function (done) { + it('TIFF known to cause rounding errors', (_t, done) => { sharp(fixtures.inputTiff) .resize(240, 320) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -206,11 +207,11 @@ describe('Resize dimensions', function () { }); }); - it('fit=inside, portrait', function (done) { + it('fit=inside, portrait', (_t, done) => { sharp(fixtures.inputTiff) .resize(320, 320, { fit: sharp.fit.inside }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -220,11 +221,11 @@ describe('Resize dimensions', function () { }); }); - it('fit=outside, portrait', function (done) { + it('fit=outside, portrait', (_t, done) => { sharp(fixtures.inputTiff) .resize(320, 320, { fit: sharp.fit.outside }) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -234,10 +235,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=inside, landscape', function (done) { + it('fit=inside, landscape', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 320, { fit: sharp.fit.inside }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -247,10 +248,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=outside, landscape', function (done) { + it('fit=outside, landscape', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 320, { fit: sharp.fit.outside }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -260,13 +261,13 @@ describe('Resize dimensions', function () { }); }); - it('fit=inside, provide only one dimension', function (done) { + it('fit=inside, provide only one dimension', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 320, fit: sharp.fit.inside }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -276,13 +277,13 @@ describe('Resize dimensions', function () { }); }); - it('fit=outside, provide only one dimension', function (done) { + it('fit=outside, provide only one dimension', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 320, fit: sharp.fit.outside }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -292,13 +293,13 @@ describe('Resize dimensions', function () { }); }); - it('Do not enlarge when input width is already less than output width', function (done) { + it('Do not enlarge when input width is already less than output width', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 2800, withoutEnlargement: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -308,13 +309,13 @@ describe('Resize dimensions', function () { }); }); - it('Do not enlarge when input height is already less than output height', function (done) { + it('Do not enlarge when input height is already less than output height', (_t, done) => { sharp(fixtures.inputJpg) .resize({ height: 2300, withoutEnlargement: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -324,14 +325,14 @@ describe('Resize dimensions', function () { }); }); - it('Do crop when fit = cover and withoutEnlargement = true and width >= outputWidth, and height < outputHeight', function (done) { + it('Do crop when fit = cover and withoutEnlargement = true and width >= outputWidth, and height < outputHeight', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 3000, height: 1000, withoutEnlargement: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -341,14 +342,14 @@ describe('Resize dimensions', function () { }); }); - it('Do crop when fit = cover and withoutEnlargement = true and width < outputWidth, and height >= outputHeight', function (done) { + it('Do crop when fit = cover and withoutEnlargement = true and width < outputWidth, and height >= outputHeight', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 1500, height: 2226, withoutEnlargement: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -358,13 +359,13 @@ describe('Resize dimensions', function () { }); }); - it('Do enlarge when input width is less than output width', function (done) { + it('Do enlarge when input width is less than output width', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 2800, withoutEnlargement: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -374,13 +375,13 @@ describe('Resize dimensions', function () { }); }); - it('Do enlarge when input width is less than output width', function (done) { + it('Do enlarge when input width is less than output width', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 2800, withoutReduction: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -390,13 +391,13 @@ describe('Resize dimensions', function () { }); }); - it('Do enlarge when input height is less than output height', function (done) { + it('Do enlarge when input height is less than output height', (_t, done) => { sharp(fixtures.inputJpg) .resize({ height: 2300, withoutReduction: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -406,13 +407,13 @@ describe('Resize dimensions', function () { }); }); - it('Do enlarge when input width is less than output width', function (done) { + it('Do enlarge when input width is less than output width', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 2800, withoutReduction: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -422,10 +423,10 @@ describe('Resize dimensions', function () { }); }); - it('Do not resize when both withoutEnlargement and withoutReduction are true', function (done) { + it('Do not resize when both withoutEnlargement and withoutReduction are true', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 320, { fit: 'fill', withoutEnlargement: true, withoutReduction: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -435,10 +436,10 @@ describe('Resize dimensions', function () { }); }); - it('Do not reduce size when fit = outside and withoutReduction are true and height > outputHeight and width > outputWidth', function (done) { + it('Do not reduce size when fit = outside and withoutReduction are true and height > outputHeight and width > outputWidth', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 320, { fit: 'outside', withoutReduction: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -448,10 +449,10 @@ describe('Resize dimensions', function () { }); }); - it('Do resize when fit = outside and withoutReduction are true and input height > height and input width > width ', function (done) { + it('Do resize when fit = outside and withoutReduction are true and input height > height and input width > width ', (_t, done) => { sharp(fixtures.inputJpg) .resize(3000, 3000, { fit: 'outside', withoutReduction: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -461,10 +462,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, downscale width and height', function (done) { + it('fit=fill, downscale width and height', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 320, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -474,13 +475,13 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, downscale width', function (done) { + it('fit=fill, downscale width', (_t, done) => { sharp(fixtures.inputJpg) .resize({ width: 320, fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -490,13 +491,13 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, downscale height', function (done) { + it('fit=fill, downscale height', (_t, done) => { sharp(fixtures.inputJpg) .resize({ height: 320, fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -506,10 +507,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, upscale width and height', function (done) { + it('fit=fill, upscale width and height', (_t, done) => { sharp(fixtures.inputJpg) .resize(3000, 3000, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -519,10 +520,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, upscale width', function (done) { + it('fit=fill, upscale width', (_t, done) => { sharp(fixtures.inputJpg) .resize(3000, null, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -532,10 +533,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, upscale height', function (done) { + it('fit=fill, upscale height', (_t, done) => { sharp(fixtures.inputJpg) .resize(null, 3000, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -545,10 +546,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, downscale width, upscale height', function (done) { + it('fit=fill, downscale width, upscale height', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 3000, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -558,10 +559,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, upscale width, downscale height', function (done) { + it('fit=fill, upscale width, downscale height', (_t, done) => { sharp(fixtures.inputJpg) .resize(3000, 320, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -571,10 +572,10 @@ describe('Resize dimensions', function () { }); }); - it('fit=fill, identity transform', function (done) { + it('fit=fill, identity transform', (_t, done) => { sharp(fixtures.inputJpg) .resize(null, null, { fit: 'fill' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -584,16 +585,16 @@ describe('Resize dimensions', function () { }); }); - it('Dimensions that result in differing even shrinks on each axis', function (done) { + it('Dimensions that result in differing even shrinks on each axis', (_t, done) => { sharp(fixtures.inputJpg) .resize(645, 399) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(645, info.width); assert.strictEqual(399, info.height); sharp(data) .resize(150, 100) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(150, info.width); assert.strictEqual(100, info.height); @@ -602,33 +603,31 @@ describe('Resize dimensions', function () { }); }); - it('Dimensions that result in differing odd shrinks on each axis', function (done) { - return sharp(fixtures.inputJpg) + it('Dimensions that result in differing odd shrinks on each axis', (_t, done) => sharp(fixtures.inputJpg) .resize(600, 399) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(600, info.width); assert.strictEqual(399, info.height); sharp(data) .resize(200) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(200, info.width); assert.strictEqual(133, info.height); fixtures.assertSimilar(fixtures.expected('resize-diff-shrink-odd.jpg'), data, done); }); - }); - }); + })); [ true, false - ].forEach(function (value) { - it(`fastShrinkOnLoad: ${value} does not causes image shifts`, function (done) { + ].forEach((value) => { + it(`fastShrinkOnLoad: ${value} does not causes image shifts`, (_t, done) => { sharp(fixtures.inputJpgCenteredImage) .resize(9, 8, { fastShrinkOnLoad: value }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(9, info.width); assert.strictEqual(8, info.height); @@ -643,11 +642,11 @@ describe('Resize dimensions', function () { sharp.kernel.mitchell, sharp.kernel.lanczos2, sharp.kernel.lanczos3 - ].forEach(function (kernel) { - it(`kernel ${kernel}`, function (done) { + ].forEach((kernel) => { + it(`kernel ${kernel}`, (_t, done) => { sharp(fixtures.inputJpg) .resize(320, null, { kernel }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -656,11 +655,11 @@ describe('Resize dimensions', function () { }); }); - it('nearest upsampling with integral factor', function (done) { + it('nearest upsampling with integral factor', (_t, done) => { sharp(fixtures.inputTiff8BitDepth) .resize(210, 210, { kernel: 'nearest' }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(210, info.width); assert.strictEqual(210, info.height); @@ -668,8 +667,7 @@ describe('Resize dimensions', function () { }); }); - it('Ensure shortest edge (height) is at least 1 pixel', function () { - return sharp({ + it('Ensure shortest edge (height) is at least 1 pixel', () => sharp({ create: { width: 10, height: 2, @@ -679,14 +677,12 @@ describe('Resize dimensions', function () { }) .resize(2) .toBuffer({ resolveWithObject: true }) - .then(function (output) { + .then((output) => { assert.strictEqual(2, output.info.width); assert.strictEqual(1, output.info.height); - }); - }); + })); - it('Ensure shortest edge (width) is at least 1 pixel', function () { - return sharp({ + it('Ensure shortest edge (width) is at least 1 pixel', () => sharp({ create: { width: 2, height: 10, @@ -696,14 +692,12 @@ describe('Resize dimensions', function () { }) .resize(null, 2) .toBuffer({ resolveWithObject: true }) - .then(function (output) { + .then((output) => { assert.strictEqual(1, output.info.width); assert.strictEqual(2, output.info.height); - }); - }); + })); - it('Ensure embedded shortest edge (height) is at least 1 pixel', function () { - return sharp({ + it('Ensure embedded shortest edge (height) is at least 1 pixel', () => sharp({ create: { width: 200, height: 1, @@ -713,14 +707,12 @@ describe('Resize dimensions', function () { }) .resize({ width: 50, height: 50, fit: sharp.fit.contain }) .toBuffer({ resolveWithObject: true }) - .then(function (output) { + .then((output) => { assert.strictEqual(50, output.info.width); assert.strictEqual(50, output.info.height); - }); - }); + })); - it('Ensure embedded shortest edge (width) is at least 1 pixel', function () { - return sharp({ + it('Ensure embedded shortest edge (width) is at least 1 pixel', () => sharp({ create: { width: 1, height: 200, @@ -730,11 +722,10 @@ describe('Resize dimensions', function () { }) .resize({ width: 50, height: 50, fit: sharp.fit.contain }) .toBuffer({ resolveWithObject: true }) - .then(function (output) { + .then((output) => { assert.strictEqual(50, output.info.width); assert.strictEqual(50, output.info.height); - }); - }); + })); it('Skip shrink-on-load where one dimension <4px', async () => { const jpeg = await sharp({ @@ -777,20 +768,20 @@ describe('Resize dimensions', function () { assert.strictEqual(height, 334); }); - it('unknown kernel throws', function () { - assert.throws(function () { + it('unknown kernel throws', () => { + assert.throws(() => { sharp().resize(null, null, { kernel: 'unknown' }); }); }); - it('unknown fit throws', function () { - assert.throws(function () { + it('unknown fit throws', () => { + assert.throws(() => { sharp().resize(null, null, { fit: 'unknown' }); }); }); - it('unknown position throws', function () { - assert.throws(function () { + it('unknown position throws', () => { + assert.throws(() => { sharp().resize(null, null, { position: 'unknown' }); }); }); @@ -798,7 +789,7 @@ describe('Resize dimensions', function () { it('Multiple resize emits warning', () => { let warningMessage = ''; const s = sharp(); - s.on('warning', function (msg) { warningMessage = msg; }); + s.on('warning', (msg) => { warningMessage = msg; }); s.resize(1); assert.strictEqual(warningMessage, ''); s.resize(2); diff --git a/test/unit/rotate.js b/test/unit/rotate.js index 7c3175504..ae8b755c0 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -1,29 +1,30 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Rotation', function () { - ['autoOrient', 'constructor'].forEach(function (rotateMethod) { +describe('Rotation', () => { + ['autoOrient', 'constructor'].forEach((rotateMethod) => { describe(`Auto orientation via ${rotateMethod}:`, () => { const options = rotateMethod === 'constructor' ? { autoOrient: true } : {}; - ['Landscape', 'Portrait'].forEach(function (orientation) { - [1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) { + ['Landscape', 'Portrait'].forEach((orientation) => { + [1, 2, 3, 4, 5, 6, 7, 8].forEach((exifTag) => { const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`]; const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`); - it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate`, function (done) { + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate`, (_t, done) => { const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; const img = sharp(input, options); rotateMethod === 'autoOrient' && img.autoOrient(); - img.toBuffer(function (err, data, info) { + img.toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(info.width, expectedWidth); assert.strictEqual(info.height, expectedHeight); @@ -31,14 +32,14 @@ describe('Rotation', function () { }); }); - it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then resize`, function (done) { + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then resize`, (_t, done) => { const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427]; const img = sharp(input, options); rotateMethod === 'autoOrient' && img.autoOrient(); img.resize({ width: 320 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(info.width, expectedWidth); assert.strictEqual(info.height, expectedHeight); @@ -47,7 +48,7 @@ describe('Rotation', function () { }); if (rotateMethod !== 'constructor') { - it(`${orientation} image with EXIF Orientation ${exifTag}: Resize then auto-rotate`, function (done) { + it(`${orientation} image with EXIF Orientation ${exifTag}: Resize then auto-rotate`, (_t, done) => { const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? (exifTag < 5) ? [320, 240] : [320, 240] : [320, 427]; @@ -56,7 +57,7 @@ describe('Rotation', function () { .resize({ width: 320 }); rotateMethod === 'autoOrient' && img.autoOrient(); - img.toBuffer(function (err, data, info) { + img.toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(info.width, expectedWidth); assert.strictEqual(info.height, expectedHeight); @@ -66,10 +67,10 @@ describe('Rotation', function () { } [true, false].forEach((doResize) => { - [90, 180, 270, 45].forEach(function (angle) { + [90, 180, 270, 45].forEach((angle) => { const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_rotate${angle}-out.jpg`); - it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then rotate ${angle} ${doResize ? 'and resize' : ''}`, function (done) { + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then rotate ${angle} ${doResize ? 'and resize' : ''}`, (_t, done) => { const [width, height] = (angle === 45 ? [742, 742] : [inputWidth, inputHeight]).map((x) => doResize ? Math.floor(x / 1.875) : x); const [expectedWidth, expectedHeight] = angle % 180 === 0 ? [width, height] : [height, width]; @@ -79,7 +80,7 @@ describe('Rotation', function () { img.rotate(angle); doResize && img.resize(expectedWidth); - img.toBuffer(function (err, data, info) { + img.toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(info.width, expectedWidth); assert.strictEqual(info.height, expectedHeight); @@ -88,11 +89,11 @@ describe('Rotation', function () { }); }); - [[true, true], [true, false], [false, true]].forEach(function ([flip, flop]) { + [[true, true], [true, false], [false, true]].forEach(([flip, flop]) => { const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; const flipFlopFileName = [flip && 'flip', flop && 'flop'].filter(Boolean).join('_'); const flipFlopTestName = [flip && 'flip', flop && 'flop'].filter(Boolean).join(' & '); - it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then ${flipFlopTestName} ${doResize ? 'and resize' : ''}`, function (done) { + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then ${flipFlopTestName} ${doResize ? 'and resize' : ''}`, (_t, done) => { const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_${flipFlopFileName}-out.jpg`); const img = sharp(input, options); @@ -103,7 +104,7 @@ describe('Rotation', function () { flop && img.flop(); doResize && img.resize(orientation === 'Landscape' ? 320 : 240); - img.toBuffer(function (err, data, info) { + img.toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(info.width, inputWidth / (doResize ? 1.875 : 1)); assert.strictEqual(info.height, inputHeight / (doResize ? 1.875 : 1)); @@ -117,12 +118,12 @@ describe('Rotation', function () { }); }); - it('Rotate by 30 degrees with semi-transparent background', function (done) { + it('Rotate by 30 degrees with semi-transparent background', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .rotate(30, { background: { r: 255, g: 0, b: 0, alpha: 0.5 } }) .png() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(408, info.width); @@ -131,11 +132,11 @@ describe('Rotation', function () { }); }); - it('Rotate by 30 degrees with solid background', function (done) { + it('Rotate by 30 degrees with solid background', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .rotate(30, { background: { r: 255, g: 0, b: 0 } }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(408, info.width); @@ -144,11 +145,11 @@ describe('Rotation', function () { }); }); - it('Rotate by 90 degrees, respecting output input size', function (done) { + it('Rotate by 90 degrees, respecting output input size', (_t, done) => { sharp(fixtures.inputJpg) .rotate(90) .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -158,11 +159,11 @@ describe('Rotation', function () { }); }); - it('Resize then rotate by 30 degrees, respecting output input size', function (done) { + it('Resize then rotate by 30 degrees, respecting output input size', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .rotate(30) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -172,9 +173,9 @@ describe('Rotation', function () { }); }); - [-3690, -450, -90, 90, 450, 3690].forEach(function (angle) { - it('Rotate by any 90-multiple angle (' + angle + 'deg)', function (done) { - sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) { + [-3690, -450, -90, 90, 450, 3690].forEach((angle) => { + it(`Rotate by any 90-multiple angle (${angle}deg)`, (_t, done) => { + sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(240, info.width); assert.strictEqual(320, info.height); @@ -183,9 +184,9 @@ describe('Rotation', function () { }); }); - [-3750, -510, -150, 30, 390, 3630].forEach(function (angle) { - it('Rotate by any 30-multiple angle (' + angle + 'deg)', function (done) { - sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) { + [-3750, -510, -150, 30, 390, 3630].forEach((angle) => { + it(`Rotate by any 30-multiple angle (${angle}deg)`, (_t, done) => { + sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(397, info.width); assert.strictEqual(368, info.height); @@ -194,9 +195,9 @@ describe('Rotation', function () { }); }); - [-3780, -540, 0, 180, 540, 3780].forEach(function (angle) { - it('Rotate by any 180-multiple angle (' + angle + 'deg)', function (done) { - sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) { + [-3780, -540, 0, 180, 540, 3780].forEach((angle) => { + it(`Rotate by any 180-multiple angle (${angle}deg)`, (_t, done) => { + sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -205,15 +206,15 @@ describe('Rotation', function () { }); }); - it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) { + it('Rotate by 270 degrees, square output ignoring aspect ratio', (_t, done) => { sharp(fixtures.inputJpg) .resize(240, 240, { fit: sharp.fit.fill }) .rotate(270) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(240, info.width); assert.strictEqual(240, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(240, metadata.width); assert.strictEqual(240, metadata.height); @@ -222,15 +223,15 @@ describe('Rotation', function () { }); }); - it('Rotate by 315 degrees, square output ignoring aspect ratio', function (done) { + it('Rotate by 315 degrees, square output ignoring aspect ratio', (_t, done) => { sharp(fixtures.inputJpg) .resize(240, 240, { fit: sharp.fit.fill }) .rotate(315) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(339, info.width); assert.strictEqual(339, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(339, metadata.width); assert.strictEqual(339, metadata.height); @@ -239,15 +240,15 @@ describe('Rotation', function () { }); }); - it('Rotate by 270 degrees, rectangular output ignoring aspect ratio', function (done) { + it('Rotate by 270 degrees, rectangular output ignoring aspect ratio', (_t, done) => { sharp(fixtures.inputJpg) .rotate(270) .resize(320, 240, { fit: sharp.fit.fill }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(320, metadata.width); assert.strictEqual(240, metadata.height); @@ -256,15 +257,15 @@ describe('Rotation', function () { }); }); - it('Auto-rotate by 270 degrees, rectangular output ignoring aspect ratio', function (done) { + it('Auto-rotate by 270 degrees, rectangular output ignoring aspect ratio', (_t, done) => { sharp(fixtures.inputJpgWithLandscapeExif8) .resize(320, 240, { fit: sharp.fit.fill }) .rotate() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(320, metadata.width); assert.strictEqual(240, metadata.height); @@ -273,15 +274,15 @@ describe('Rotation', function () { }); }); - it('Rotate by 30 degrees, rectangular output ignoring aspect ratio', function (done) { + it('Rotate by 30 degrees, rectangular output ignoring aspect ratio', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240, { fit: sharp.fit.fill }) .rotate(30) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(397, info.width); assert.strictEqual(368, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(397, metadata.width); assert.strictEqual(368, metadata.height); @@ -290,17 +291,17 @@ describe('Rotation', function () { }); }); - it('Input image has Orientation EXIF tag but do not rotate output', function (done) { + it('Input image has Orientation EXIF tag but do not rotate output', (_t, done) => { sharp(fixtures.inputJpgWithExif) .resize(320) .withMetadata() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(427, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(8, metadata.orientation); done(); @@ -308,11 +309,11 @@ describe('Rotation', function () { }); }); - it('Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate', function (done) { + it('Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate', (_t, done) => { sharp(fixtures.inputJpgWithExif) .rotate() .resize(320) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -321,17 +322,17 @@ describe('Rotation', function () { }); }); - it('Override EXIF Orientation tag metadata after auto-rotate', function (done) { + it('Override EXIF Orientation tag metadata after auto-rotate', (_t, done) => { sharp(fixtures.inputJpgWithExif) .rotate() .resize(320) .withMetadata({ orientation: 3 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(3, metadata.orientation); fixtures.assertSimilar(fixtures.expected('exif-8.jpg'), data, done); @@ -339,17 +340,17 @@ describe('Rotation', function () { }); }); - it('Input image has Orientation EXIF tag value of 5 (270 degrees + flip), auto-rotate', function (done) { + it('Input image has Orientation EXIF tag value of 5 (270 degrees + flip), auto-rotate', (_t, done) => { sharp(fixtures.inputJpgWithExifMirroring) .rotate() .resize(320) .withMetadata() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(1, metadata.orientation); fixtures.assertSimilar(fixtures.expected('exif-5.jpg'), data, done); @@ -357,8 +358,8 @@ describe('Rotation', function () { }); }); - it('Attempt to auto-rotate using image that has no EXIF', function (done) { - sharp(fixtures.inputJpg).rotate().resize(320).toBuffer(function (err, data, info) { + it('Attempt to auto-rotate using image that has no EXIF', (_t, done) => { + sharp(fixtures.inputJpg).rotate().resize(320).toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -368,12 +369,12 @@ describe('Rotation', function () { }); }); - it('Attempt to auto-rotate image format without EXIF support', function (done) { + it('Attempt to auto-rotate image format without EXIF support', (_t, done) => { sharp(fixtures.inputPng) .rotate() .resize(320) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); @@ -383,8 +384,8 @@ describe('Rotation', function () { }); }); - it('Rotate with a string argument, should fail', function () { - assert.throws(function () { + it('Rotate with a string argument, should fail', () => { + assert.throws(() => { sharp(fixtures.inputJpg).rotate('not-a-number'); }); }); @@ -435,18 +436,18 @@ describe('Rotation', function () { it('Multiple rotate emits warning', () => { let warningMessage = ''; const s = sharp(); - s.on('warning', function (msg) { warningMessage = msg; }); + s.on('warning', (msg) => { warningMessage = msg; }); s.rotate(90); assert.strictEqual(warningMessage, ''); s.rotate(180); assert.strictEqual(warningMessage, 'ignoring previous rotate options'); }); - it('Multiple rotate: last one wins (cardinal)', function (done) { + it('Multiple rotate: last one wins (cardinal)', (_t, done) => { sharp(fixtures.inputJpg) .rotate(45) .rotate(90) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(2225, info.width); assert.strictEqual(2725, info.height); @@ -454,11 +455,11 @@ describe('Rotation', function () { }); }); - it('Multiple rotate: last one wins (non cardinal)', function (done) { + it('Multiple rotate: last one wins (non cardinal)', (_t, done) => { sharp(fixtures.inputJpg) .rotate(90) .rotate(45) - .toBuffer(function (err, data, info) { + .toBuffer((err, _data, info) => { if (err) throw err; assert.strictEqual(3500, info.width); assert.strictEqual(3500, info.height); @@ -466,17 +467,17 @@ describe('Rotation', function () { }); }); - it('Flip - vertical', function (done) { + it('Flip - vertical', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .flip() .withMetadata() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(261, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(1, metadata.orientation); fixtures.assertSimilar(fixtures.expected('flip.jpg'), data, done); @@ -484,17 +485,17 @@ describe('Rotation', function () { }); }); - it('Flop - horizontal', function (done) { + it('Flop - horizontal', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .flop() .withMetadata() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(261, info.height); - sharp(data).metadata(function (err, metadata) { + sharp(data).metadata((err, metadata) => { if (err) throw err; assert.strictEqual(1, metadata.orientation); fixtures.assertSimilar(fixtures.expected('flop.jpg'), data, done); @@ -502,12 +503,12 @@ describe('Rotation', function () { }); }); - it('Flip and flop', function (done) { + it('Flip and flop', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .flip() .flop() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -516,12 +517,12 @@ describe('Rotation', function () { }); }); - it('Neither flip nor flop', function (done) { + it('Neither flip nor flop', (_t, done) => { sharp(fixtures.inputJpg) .resize(320) .flip(false) .flop(false) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -530,12 +531,12 @@ describe('Rotation', function () { }); }); - it('Auto-rotate and flip', function (done) { + it('Auto-rotate and flip', (_t, done) => { sharp(fixtures.inputJpgWithExif) .rotate() .flip() .resize(320) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -544,12 +545,12 @@ describe('Rotation', function () { }); }); - it('Auto-rotate and flop', function (done) { + it('Auto-rotate and flop', (_t, done) => { sharp(fixtures.inputJpgWithExif) .rotate() .flop() .resize(320) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -565,9 +566,9 @@ describe('Rotation', function () { .raw() .toBuffer(); - assert.strictEqual(r, 60); - assert.strictEqual(g, 73); - assert.strictEqual(b, 52); + assert.strictEqual(r, 61); + assert.strictEqual(g, 74); + assert.strictEqual(b, 51); }); it('Flip and rotate ordering', async () => { @@ -635,6 +636,40 @@ describe('Rotation', function () { assert.strictEqual(height, 6); }); + it('Shrink-on-load with autoOrient', async () => { + const data = await sharp(fixtures.inputJpgWithLandscapeExif6) + .resize(8) + .autoOrient() + .avif({ effort: 0 }) + .toBuffer(); + + const { width, height, orientation } = await sharp(data).metadata(); + assert.strictEqual(width, 8); + assert.strictEqual(height, 6); + assert.strictEqual(orientation, undefined); + }); + + it('Auto-orient and rotate 45', async () => { + const data = await sharp(fixtures.inputJpgWithLandscapeExif2, { autoOrient: true }) + .rotate(45) + .toBuffer(); + + const { width, height } = await sharp(data).metadata(); + assert.strictEqual(width, 742); + assert.strictEqual(height, 742); + }); + + it('Auto-orient, extract and rotate 45', async () => { + const data = await sharp(fixtures.inputJpgWithLandscapeExif2, { autoOrient: true }) + .extract({ left: 20, top: 20, width: 200, height: 100 }) + .rotate(45) + .toBuffer(); + + const { width, height } = await sharp(data).metadata(); + assert.strictEqual(width, 212); + assert.strictEqual(height, 212); + }); + it('Invalid autoOrient throws', () => assert.throws( () => sharp({ autoOrient: 'fail' }), diff --git a/test/unit/sharpen.js b/test/unit/sharpen.js index 8d447ba18..9ea354a71 100644 --- a/test/unit/sharpen.js +++ b/test/unit/sharpen.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Sharpen', function () { - it('specific radius 10 (sigma 6)', function (done) { +describe('Sharpen', () => { + it('specific radius 10 (sigma 6)', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen(6) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -22,11 +23,11 @@ describe('Sharpen', function () { }); }); - it('specific radius 3 (sigma 1.5) and levels 0.5, 2.5', function (done) { + it('specific radius 3 (sigma 1.5) and levels 0.5, 2.5', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen(1.5, 0.5, 2.5) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -35,11 +36,11 @@ describe('Sharpen', function () { }); }); - it('specific radius 5 (sigma 3.5) and levels 2, 4', function (done) { + it('specific radius 5 (sigma 3.5) and levels 2, 4', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen(3.5, 2, 4) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -48,7 +49,7 @@ describe('Sharpen', function () { }); }); - it('sigma=3.5, m1=2, m2=4', (done) => { + it('sigma=3.5, m1=2, m2=4', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen({ sigma: 3.5, m1: 2, m2: 4 }) @@ -56,7 +57,7 @@ describe('Sharpen', function () { .then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done)); }); - it('sigma=3.5, m1=2, m2=4, x1=2, y2=5, y3=25', (done) => { + it('sigma=3.5, m1=2, m2=4, x1=2, y2=5, y3=25', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen({ sigma: 3.5, m1: 2, m2: 4, x1: 2, y2: 5, y3: 25 }) @@ -65,11 +66,11 @@ describe('Sharpen', function () { }); if (!process.env.SHARP_TEST_WITHOUT_CACHE) { - it('specific radius/levels with alpha channel', function (done) { + it('specific radius/levels with alpha channel', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 240) .sharpen(5, 4, 8) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(4, info.channels); @@ -80,11 +81,11 @@ describe('Sharpen', function () { }); } - it('mild sharpen', function (done) { + it('mild sharpen', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -93,20 +94,20 @@ describe('Sharpen', function () { }); }); - it('invalid sigma', function () { - assert.throws(function () { + it('invalid sigma', () => { + assert.throws(() => { sharp(fixtures.inputJpg).sharpen(-1.5); }); }); - it('invalid flat', function () { - assert.throws(function () { + it('invalid flat', () => { + assert.throws(() => { sharp(fixtures.inputJpg).sharpen(1, -1); }); }); - it('invalid jagged', function () { - assert.throws(function () { + it('invalid jagged', () => { + assert.throws(() => { sharp(fixtures.inputJpg).sharpen(1, 1, -1); }); }); @@ -141,11 +142,11 @@ describe('Sharpen', function () { /Expected number between 0 and 1000000 for options\.y3 but received -1 of type number/ )); - it('sharpened image is larger than non-sharpened', function (done) { + it('sharpened image is larger than non-sharpened', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen(false) - .toBuffer(function (err, notSharpened, info) { + .toBuffer((err, notSharpened, info) => { if (err) throw err; assert.strictEqual(true, notSharpened.length > 0); assert.strictEqual('jpeg', info.format); @@ -154,7 +155,7 @@ describe('Sharpen', function () { sharp(fixtures.inputJpg) .resize(320, 240) .sharpen(true) - .toBuffer(function (err, sharpened, info) { + .toBuffer((err, sharpened, info) => { if (err) throw err; assert.strictEqual(true, sharpened.length > 0); assert.strictEqual(true, sharpened.length > notSharpened.length); diff --git a/test/unit/stats.js b/test/unit/stats.js index 487e37dd4..9164787e3 100644 --- a/test/unit/stats.js +++ b/test/unit/stats.js @@ -1,10 +1,11 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); @@ -21,9 +22,9 @@ function isInteger (val) { return Number.isInteger(val); } -describe('Image Stats', function () { - it('JPEG', function (done) { - sharp(fixtures.inputJpg).stats(function (err, stats) { +describe('Image Stats', () => { + it('JPEG', (_t, done) => { + sharp(fixtures.inputJpg).stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -87,8 +88,8 @@ describe('Image Stats', function () { }); }); - it('PNG without transparency', function (done) { - sharp(fixtures.inputPng).stats(function (err, stats) { + it('PNG without transparency', (_t, done) => { + sharp(fixtures.inputPng).stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -119,8 +120,8 @@ describe('Image Stats', function () { }); }); - it('PNG with transparency', function (done) { - sharp(fixtures.inputPngWithTransparency).stats(function (err, stats) { + it('PNG with transparency', (_t, done) => { + sharp(fixtures.inputPngWithTransparency).stats((err, stats) => { if (err) throw err; assert.strictEqual(false, stats.isOpaque); @@ -200,8 +201,8 @@ describe('Image Stats', function () { }); }); - it('PNG fully transparent', function (done) { - sharp(fixtures.inputPngCompleteTransparency).stats(function (err, stats) { + it('PNG fully transparent', (_t, done) => { + sharp(fixtures.inputPngCompleteTransparency).stats((err, stats) => { if (err) throw err; assert.strictEqual(false, stats.isOpaque); @@ -233,8 +234,8 @@ describe('Image Stats', function () { }); }); - it('Tiff', function (done) { - sharp(fixtures.inputTiff).stats(function (err, stats) { + it('Tiff', (_t, done) => { + sharp(fixtures.inputTiff).stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -266,8 +267,8 @@ describe('Image Stats', function () { }); }); - it('WebP', function (done) { - sharp(fixtures.inputWebP).stats(function (err, stats) { + it('WebP', (_t, done) => { + sharp(fixtures.inputWebP).stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -331,8 +332,8 @@ describe('Image Stats', function () { }); }); - it('GIF', function (done) { - sharp(fixtures.inputGif).stats(function (err, stats) { + it('GIF', (_t, done) => { + sharp(fixtures.inputGif).stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -396,8 +397,8 @@ describe('Image Stats', function () { }); }); - it('Grayscale GIF with alpha', function (done) { - sharp(fixtures.inputGifGreyPlusAlpha).stats(function (err, stats) { + it('Grayscale GIF with alpha', (_t, done) => { + sharp(fixtures.inputGifGreyPlusAlpha).stats((err, stats) => { if (err) throw err; assert.strictEqual(false, stats.isOpaque); @@ -478,9 +479,9 @@ describe('Image Stats', function () { assert.strictEqual(sharpness, 0); }); - it('Stream in, Callback out', function (done) { + it('Stream in, Callback out', (_t, done) => { const readable = fs.createReadStream(fixtures.inputJpg); - const pipeline = sharp().stats(function (err, stats) { + const pipeline = sharp().stats((err, stats) => { if (err) throw err; assert.strictEqual(true, stats.isOpaque); @@ -545,12 +546,12 @@ describe('Image Stats', function () { readable.pipe(pipeline); }); - it('Stream in, Promise out', function () { + it('Stream in, Promise out', () => { const pipeline = sharp(); fs.createReadStream(fixtures.inputJpg).pipe(pipeline); - return pipeline.stats().then(function (stats) { + return pipeline.stats().then((stats) => { assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.332915340666659)); assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569)); @@ -607,13 +608,12 @@ describe('Image Stats', function () { assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725)); assert.strictEqual(true, isInteger(stats.channels[0].maxY)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725)); - }).catch(function (err) { + }).catch((err) => { throw err; }); }); - it('File in, Promise out', function () { - return sharp(fixtures.inputJpg).stats().then(function (stats) { + it('File in, Promise out', () => sharp(fixtures.inputJpg).stats().then((stats) => { assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.332915340666659)); assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569)); @@ -670,10 +670,9 @@ describe('Image Stats', function () { assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725)); assert.strictEqual(true, isInteger(stats.channels[0].maxY)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725)); - }).catch(function (err) { + }).catch((err) => { throw err; - }); - }); + })); it('Blurred image has lower sharpness than original', () => { const original = sharp(fixtures.inputJpg).stats(); @@ -687,9 +686,9 @@ describe('Image Stats', function () { }); }); - it('File input with corrupt header fails gracefully', function (done) { + it('File input with corrupt header fails gracefully', (_t, done) => { sharp(fixtures.inputJpgWithCorruptHeader) - .stats(function (err) { + .stats((err) => { assert(err.message.includes('Input file has corrupt header')); assert(err.stack.includes('at Sharp.stats')); assert(err.stack.includes(__filename)); @@ -697,10 +696,10 @@ describe('Image Stats', function () { }); }); - it('Stream input with corrupt header fails gracefully', function (done) { + it('Stream input with corrupt header fails gracefully', (_t, done) => { fs.createReadStream(fixtures.inputJpgWithCorruptHeader).pipe( sharp() - .stats(function (err) { + .stats((err) => { assert(err.message.includes('Input buffer has corrupt header')); assert(err.stack.includes('at Sharp.stats')); assert(err.stack.includes(__filename)); @@ -709,40 +708,38 @@ describe('Image Stats', function () { ); }); - it('File input with corrupt header fails gracefully, Promise out', function () { - return sharp(fixtures.inputJpgWithCorruptHeader) - .stats().then(function (stats) { + it('File input with corrupt header fails gracefully, Promise out', () => sharp(fixtures.inputJpgWithCorruptHeader) + .stats().then(() => { throw new Error('Corrupt Header file'); - }).catch(function (err) { + }).catch((err) => { assert.ok(!!err); - }); - }); + })); - it('File input with corrupt header fails gracefully, Stream In, Promise Out', function () { + it('File input with corrupt header fails gracefully, Stream In, Promise Out', () => { const pipeline = sharp(); fs.createReadStream(fixtures.inputJpgWithCorruptHeader).pipe(pipeline); return pipeline - .stats().then(function (stats) { + .stats().then(() => { throw new Error('Corrupt Header file'); - }).catch(function (err) { + }).catch((err) => { assert.ok(!!err); }); }); - it('Buffer input with corrupt header fails gracefully', function (done) { + it('Buffer input with corrupt header fails gracefully', (_t, done) => { sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader)) - .stats(function (err) { + .stats((err) => { assert.strictEqual(true, !!err); done(); }); }); - it('Non-existent file in, Promise out', function (done) { - sharp('fail').stats().then(function (stats) { + it('Non-existent file in, Promise out', (_t, done) => { + sharp('fail').stats().then(() => { throw new Error('Non-existent file'); - }, function (err) { + }, (err) => { assert.ok(!!err); done(); }); diff --git a/test/unit/svg.js b/test/unit/svg.js index 8f147b0a7..d11b61895 100644 --- a/test/unit/svg.js +++ b/test/unit/svg.js @@ -1,28 +1,29 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('SVG input', function () { - it('Convert SVG to PNG at default 72DPI', function (done) { +describe('SVG input', () => { + it('Convert SVG to PNG at default 72DPI', (_t, done) => { sharp(fixtures.inputSvg) .resize(1024) .extract({ left: 290, top: 760, width: 40, height: 40 }) .toFormat('png') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(40, info.width); assert.strictEqual(40, info.height); - fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function (err) { + fixtures.assertSimilar(fixtures.expected('svg72.png'), data, (err) => { if (err) throw err; - sharp(data).metadata(function (err, info) { + sharp(data).metadata((err, info) => { if (err) throw err; assert.strictEqual(72, info.density); done(); @@ -31,19 +32,19 @@ describe('SVG input', function () { }); }); - it('Convert SVG to PNG at 1200DPI', function (done) { + it('Convert SVG to PNG at 1200DPI', (_t, done) => { sharp(fixtures.inputSvg, { density: 1200 }) .resize(1024) .extract({ left: 290, top: 760, width: 40, height: 40 }) .toFormat('png') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(40, info.width); assert.strictEqual(40, info.height); - fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function (err) { + fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, (err) => { if (err) throw err; - sharp(data).metadata(function (err, info) { + sharp(data).metadata((err, info) => { if (err) throw err; assert.strictEqual(1200, info.density); done(); @@ -52,22 +53,22 @@ describe('SVG input', function () { }); }); - it('Convert SVG to PNG at DPI larger than 2400', function (done) { + it('Convert SVG to PNG at DPI larger than 2400', (_t, done) => { const size = 1024; - sharp(fixtures.inputSvgSmallViewBox).metadata(function (err, metadata) { + sharp(fixtures.inputSvgSmallViewBox).metadata((err, metadata) => { if (err) throw err; const density = (size / Math.max(metadata.width, metadata.height)) * metadata.density; sharp(fixtures.inputSvgSmallViewBox, { density }) .resize(size) .toFormat('png') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(size, info.width); assert.strictEqual(size, info.height); - fixtures.assertSimilar(fixtures.expected('circle.png'), data, function (err) { + fixtures.assertSimilar(fixtures.expected('circle.png'), data, (err) => { if (err) throw err; - sharp(data).metadata(function (err, info) { + sharp(data).metadata((err, info) => { if (err) throw err; assert.strictEqual(9216, info.density); done(); @@ -77,19 +78,19 @@ describe('SVG input', function () { }); }); - it('Convert SVG to PNG utilizing scale-on-load', function (done) { + it('Convert SVG to PNG utilizing scale-on-load', (_t, done) => { const size = 1024; sharp(fixtures.inputSvgSmallViewBox) .resize(size) .toFormat('png') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(size, info.width); assert.strictEqual(size, info.height); - fixtures.assertSimilar(fixtures.expected('circle.png'), data, function (err) { + fixtures.assertSimilar(fixtures.expected('circle.png'), data, (err) => { if (err) throw err; - sharp(data).metadata(function (err, info) { + sharp(data).metadata((err, info) => { if (err) throw err; assert.strictEqual(72, info.density); done(); @@ -98,24 +99,24 @@ describe('SVG input', function () { }); }); - it('Convert SVG to PNG at 14.4DPI', function (done) { + it('Convert SVG to PNG at 14.4DPI', (_t, done) => { sharp(fixtures.inputSvg, { density: 14.4 }) .toFormat('png') - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(20, info.width); assert.strictEqual(20, info.height); - fixtures.assertSimilar(fixtures.expected('svg14.4.png'), data, function (err) { + fixtures.assertSimilar(fixtures.expected('svg14.4.png'), data, (err) => { if (err) throw err; done(); }); }); }); - it('Convert SVG with embedded images to PNG, respecting dimensions, autoconvert to PNG', function (done) { + it('Convert SVG with embedded images to PNG, respecting dimensions, autoconvert to PNG', (_t, done) => { sharp(fixtures.inputSvgWithEmbeddedImages) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(480, info.width); @@ -139,6 +140,41 @@ describe('SVG input', function () { assert.strictEqual(info.channels, 4); }); + it('Can apply custom CSS', async () => { + const 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', () => assert.rejects( () => sharp(Buffer.from('')).toBuffer(), diff --git a/test/unit/text.js b/test/unit/text.js index a81a88c93..029b45f1f 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -1,18 +1,17 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); const { inRange } = require('../../lib/is'); -describe('Text to image', function () { - this.retries(3); - - it('text with default values', async function () { +describe('Text to image', () => { + it('text with default values', async (t) => { const output = fixtures.path('output.text-default.png'); const text = sharp({ text: { @@ -20,7 +19,7 @@ describe('Text to image', function () { } }); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } const info = await text.png().toFile(output); assert.strictEqual('png', info.format); @@ -42,7 +41,7 @@ describe('Text to image', function () { assert.ok(info.textAutofitDpi > 0); }); - it('text with width and height', function (done) { + it('text with width and height', async (t) => { const output = fixtures.path('output.text-width-height.png'); const text = sharp({ text: { @@ -52,20 +51,17 @@ describe('Text to image', function () { } }); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } - text.toFile(output, function (err, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(3, info.channels); - assert.ok(inRange(info.width, 400, 600), `Actual width ${info.width}`); - assert.ok(inRange(info.height, 300, 500), `Actual height ${info.height}`); - assert.ok(inRange(info.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`); - done(); - }); + const info = await text.toFile(output); + assert.strictEqual('png', info.format); + assert.strictEqual(3, info.channels); + 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.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`); }); - it('text with dpi', function (done) { + it('text with dpi', async (t) => { const output = fixtures.path('output.text-dpi.png'); const dpi = 300; const text = sharp({ @@ -75,20 +71,15 @@ describe('Text to image', function () { } }); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } - text.toFile(output, function (err, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - sharp(output).metadata(function (err, metadata) { - if (err) throw err; - assert.strictEqual(dpi, metadata.density); - done(); - }); - }); + const info = await text.toFile(output); + assert.strictEqual('png', info.format); + const metadata = await sharp(output).metadata(); + assert.strictEqual(dpi, metadata.density); }); - it('text with color and pango markup', function (done) { + it('text with color and pango markup', async (t) => { const output = fixtures.path('output.text-color-pango.png'); const dpi = 300; const text = sharp({ @@ -99,23 +90,18 @@ describe('Text to image', function () { } }); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } - text.toFile(output, function (err, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(4, info.channels); - sharp(output).metadata(function (err, metadata) { - if (err) throw err; - assert.strictEqual(dpi, metadata.density); - assert.strictEqual('uchar', metadata.depth); - assert.strictEqual(true, metadata.hasAlpha); - done(); - }); - }); + const info = await text.toFile(output); + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + const metadata = await sharp(output).metadata(); + assert.strictEqual(dpi, metadata.density); + assert.strictEqual('uchar', metadata.depth); + assert.strictEqual(true, metadata.hasAlpha); }); - it('text with font', function (done) { + it('text with font', async (t) => { const output = fixtures.path('output.text-with-font.png'); const text = sharp({ text: { @@ -124,19 +110,16 @@ describe('Text to image', function () { } }); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } - text.toFile(output, function (err, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(3, info.channels); - assert.ok(info.width > 30); - assert.ok(info.height > 10); - done(); - }); + const info = await text.toFile(output); + assert.strictEqual('png', info.format); + assert.strictEqual(3, info.channels); + assert.ok(info.width > 30); + assert.ok(info.height > 10); }); - it('text with justify and composite', function (done) { + it('text with justify and composite', async (t) => { const output = fixtures.path('output.text-composite.png'); const width = 500; const dpi = 300; @@ -168,26 +151,21 @@ describe('Text to image', function () { top: 250 }]); if (!sharp.versions.pango) { - return this.skip(); + return t.skip(); } - text.toFile(output, function (err, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(4, info.channels); - assert.strictEqual(width, info.width); - assert.strictEqual(true, info.premultiplied); - sharp(output).metadata(function (err, metadata) { - if (err) throw err; - assert.strictEqual('srgb', metadata.space); - assert.strictEqual('uchar', metadata.depth); - assert.strictEqual(true, metadata.hasAlpha); - done(); - }); - }); + const info = await text.toFile(output); + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + assert.strictEqual(width, info.width); + assert.strictEqual(true, info.premultiplied); + const metadata = await sharp(output).metadata(); + assert.strictEqual('srgb', metadata.space); + assert.strictEqual('uchar', metadata.depth); + assert.strictEqual(true, metadata.hasAlpha); }); - it('bad text input', function () { - assert.throws(function () { + it('bad text input', () => { + assert.throws(() => { sharp({ text: { } @@ -195,8 +173,8 @@ describe('Text to image', function () { }); }); - it('fontfile input', function () { - assert.doesNotThrow(function () { + it('fontfile input', () => { + assert.doesNotThrow(() => { sharp({ text: { text: 'text', @@ -206,8 +184,8 @@ describe('Text to image', function () { }); }); - it('bad font input', function () { - assert.throws(function () { + it('bad font input', () => { + assert.throws(() => { sharp({ text: { text: 'text', @@ -217,8 +195,8 @@ describe('Text to image', function () { }); }); - it('bad fontfile input', function () { - assert.throws(function () { + it('bad fontfile input', () => { + assert.throws(() => { sharp({ text: { text: 'text', @@ -258,8 +236,8 @@ describe('Text to image', function () { ); }); - it('bad align input', function () { - assert.throws(function () { + it('bad align input', () => { + assert.throws(() => { sharp({ text: { text: 'text', @@ -269,8 +247,8 @@ describe('Text to image', function () { }); }); - it('bad justify input', function () { - assert.throws(function () { + it('bad justify input', () => { + assert.throws(() => { sharp({ text: { text: 'text', @@ -295,8 +273,8 @@ describe('Text to image', function () { ); }); - it('bad rgba input', function () { - assert.throws(function () { + it('bad rgba input', () => { + assert.throws(() => { sharp({ text: { text: 'text', @@ -321,8 +299,8 @@ describe('Text to image', function () { ); }); - it('only height or dpi not both', function () { - assert.throws(function () { + it('only height or dpi not both', () => { + assert.throws(() => { sharp({ text: { text: 'text', diff --git a/test/unit/threshold.js b/test/unit/threshold.js index 1f93d659e..1ffb0f9cc 100644 --- a/test/unit/threshold.js +++ b/test/unit/threshold.js @@ -1,19 +1,20 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Threshold', function () { - it('threshold 1 jpeg', function (done) { +describe('Threshold', () => { + it('threshold 1 jpeg', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(1) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -22,11 +23,11 @@ describe('Threshold', function () { }); }); - it('threshold 40 jpeg', function (done) { + it('threshold 40 jpeg', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(40) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -35,11 +36,11 @@ describe('Threshold', function () { }); }); - it('threshold 128', function (done) { + it('threshold 128', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(128) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -48,11 +49,11 @@ describe('Threshold', function () { }); }); - it('threshold true (=128)', function (done) { + it('threshold true (=128)', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(true) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -61,20 +62,20 @@ describe('Threshold', function () { }); }); - it('threshold false (=0)', function (done) { + it('threshold false (=0)', (_t, done) => { sharp(fixtures.inputJpg) .threshold(false) - .toBuffer(function (err, data, info) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.inputJpg, data, done); }); }); - it('threshold grayscale: true (=128)', function (done) { + it('threshold grayscale: true (=128)', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(128, { grayscale: true }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -83,11 +84,11 @@ describe('Threshold', function () { }); }); - it('threshold default jpeg', function (done) { + it('threshold default jpeg', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -96,11 +97,11 @@ describe('Threshold', function () { }); }); - it('threshold default png transparency', function (done) { + it('threshold default png transparency', (_t, done) => { sharp(fixtures.inputPngWithTransparency) .resize(320, 240) .threshold() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -109,11 +110,11 @@ describe('Threshold', function () { }); }); - it('threshold default png alpha', function (done) { + it('threshold default png alpha', (_t, done) => { sharp(fixtures.inputPngWithGreyAlpha) .resize(320, 240) .threshold() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('png', info.format); assert.strictEqual(320, info.width); @@ -122,21 +123,21 @@ describe('Threshold', function () { }); }); - it('threshold default webp transparency', function (done) { + it('threshold default webp transparency', (_t, done) => { sharp(fixtures.inputWebPWithTransparency) .threshold() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('webp', info.format); fixtures.assertSimilar(fixtures.expected('threshold-128-transparency.webp'), data, done); }); }); - it('color threshold', function (done) { + it('color threshold', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .threshold(128, { grayscale: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -145,14 +146,14 @@ describe('Threshold', function () { }); }); - it('invalid threshold -1', function () { - assert.throws(function () { + it('invalid threshold -1', () => { + assert.throws(() => { sharp().threshold(-1); }); }); - it('invalid threshold 256', function () { - assert.throws(function () { + it('invalid threshold 256', () => { + assert.throws(() => { sharp().threshold(256); }); }); diff --git a/test/unit/tiff.js b/test/unit/tiff.js index dab8948af..be5e5535f 100644 --- a/test/unit/tiff.js +++ b/test/unit/tiff.js @@ -1,23 +1,24 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); const outputTiff = fixtures.path('output.tiff'); -describe('TIFF', function () { - it('Load TIFF from Buffer', function (done) { +describe('TIFF', () => { + it('Load TIFF from Buffer', (_t, done) => { const inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); sharp(inputTiffBuffer) .resize(320, 240) .jpeg() - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -28,10 +29,10 @@ describe('TIFF', function () { }); }); - it('Load multi-page TIFF from file', function (done) { + it('Load multi-page TIFF from file', (_t, done) => { sharp(fixtures.inputTiffMultipage) // defaults to page 0 .jpeg() - .toBuffer(function (err, defaultData, defaultInfo) { + .toBuffer((err, defaultData, defaultInfo) => { if (err) throw err; assert.strictEqual(true, defaultData.length > 0); assert.strictEqual(defaultData.length, defaultInfo.size); @@ -39,7 +40,7 @@ describe('TIFF', function () { sharp(fixtures.inputTiffMultipage, { page: 1 }) // 50%-scale copy of page 0 .jpeg() - .toBuffer(function (err, scaledData, scaledInfo) { + .toBuffer((err, scaledData, scaledInfo) => { if (err) throw err; assert.strictEqual(true, scaledData.length > 0); assert.strictEqual(scaledData.length, scaledInfo.size); @@ -51,11 +52,11 @@ describe('TIFF', function () { }); }); - it('Load multi-page TIFF from Buffer', function (done) { + it('Load multi-page TIFF from Buffer', (_t, done) => { const inputTiffBuffer = fs.readFileSync(fixtures.inputTiffMultipage); sharp(inputTiffBuffer) // defaults to page 0 .jpeg() - .toBuffer(function (err, defaultData, defaultInfo) { + .toBuffer((err, defaultData, defaultInfo) => { if (err) throw err; assert.strictEqual(true, defaultData.length > 0); assert.strictEqual(defaultData.length, defaultInfo.size); @@ -63,7 +64,7 @@ describe('TIFF', function () { sharp(inputTiffBuffer, { page: 1 }) // 50%-scale copy of page 0 .jpeg() - .toBuffer(function (err, scaledData, scaledInfo) { + .toBuffer((err, scaledData, scaledInfo) => { if (err) throw err; assert.strictEqual(true, scaledData.length > 0); assert.strictEqual(scaledData.length, scaledInfo.size); @@ -75,10 +76,10 @@ describe('TIFF', function () { }); }); - it('Save TIFF to Buffer', function (done) { + it('Save TIFF to Buffer', (_t, done) => { sharp(fixtures.inputTiff) .resize(320, 240) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(data.length, info.size); @@ -104,19 +105,19 @@ describe('TIFF', function () { ) ); - it('Invalid TIFF quality throws error', function () { - assert.throws(function () { + it('Invalid TIFF quality throws error', () => { + assert.throws(() => { sharp().tiff({ quality: 101 }); }); }); - it('Missing TIFF quality does not throw error', function () { - assert.doesNotThrow(function () { + it('Missing TIFF quality does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff(); }); }); - it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) { + it('Not squashing TIFF to a bit depth of 1 should not change the file size', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; sharp(fixtures.inputTiff8BitDepth) .toColourspace('b-w') // can only squash 1 band uchar images @@ -133,7 +134,7 @@ describe('TIFF', function () { }); }); - it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) { + it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; sharp(fixtures.inputTiff8BitDepth) .toColourspace('b-w') // can only squash 1 band uchar images @@ -150,8 +151,8 @@ describe('TIFF', function () { }); }); - it('Invalid TIFF bitdepth value throws error', function () { - assert.throws(function () { + it('Invalid TIFF bitdepth value throws error', () => { + assert.throws(() => { sharp().tiff({ bitdepth: 3 }); }, /Error: Expected 1, 2, 4 or 8 for bitdepth but received 3 of type number/); }); @@ -209,19 +210,19 @@ describe('TIFF', function () { assert.strictEqual(25400, density); }); - it('TIFF invalid xres value should throw an error', function () { - assert.throws(function () { + it('TIFF invalid xres value should throw an error', () => { + assert.throws(() => { sharp().tiff({ xres: '1000.0' }); }); }); - it('TIFF invalid yres value should throw an error', function () { - assert.throws(function () { + it('TIFF invalid yres value should throw an error', () => { + assert.throws(() => { sharp().tiff({ yres: '1000.0' }); }); }); - it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) { + it('TIFF lzw compression with horizontal predictor shrinks test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -273,7 +274,7 @@ describe('TIFF', function () { }) ); - it('TIFF ccittfax4 compression shrinks b-w test file', function (done) { + it('TIFF ccittfax4 compression shrinks b-w test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiff).size; sharp(fixtures.inputTiff) .toColourspace('b-w') @@ -313,7 +314,7 @@ describe('TIFF', function () { assert.strictEqual(resolutionUnit, 'cm'); }); - it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) { + it('TIFF deflate compression with horizontal predictor shrinks test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -328,7 +329,7 @@ describe('TIFF', function () { }); }); - it('TIFF deflate compression with float predictor shrinks test file', function (done) { + it('TIFF deflate compression with float predictor shrinks test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -343,7 +344,7 @@ describe('TIFF', function () { }); }); - it('TIFF deflate compression without predictor shrinks test file', function (done) { + it('TIFF deflate compression without predictor shrinks test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -358,7 +359,7 @@ describe('TIFF', function () { }); }); - it('TIFF jpeg compression shrinks test file', function (done) { + it('TIFF jpeg compression shrinks test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -372,67 +373,79 @@ describe('TIFF', function () { }); }); - it('TIFF none compression does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF none compression does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ compression: 'none' }); }); }); - it('TIFF lzw compression does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF lzw compression does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ compression: 'lzw' }); }); }); - it('TIFF deflate compression does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF deflate compression does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ compression: 'deflate' }); }); }); - it('TIFF invalid compression option throws', function () { - assert.throws(function () { + it('TIFF invalid compression option throws', () => { + assert.throws(() => { sharp().tiff({ compression: 0 }); }); }); - it('TIFF invalid compression option throws', function () { - assert.throws(function () { + it('TIFF invalid compression option throws', () => { + assert.throws(() => { sharp().tiff({ compression: 'a' }); }); }); - it('TIFF invalid predictor option throws', function () { - assert.throws(function () { + it('TIFF bigtiff true value does not throw error', () => { + assert.doesNotThrow(() => { + sharp().tiff({ bigtiff: true }); + }); + }); + + it('Invalid TIFF bigtiff value throws error', () => { + assert.throws(() => { + sharp().tiff({ bigtiff: 'true' }); + }); + }); + + it('TIFF invalid predictor option throws', () => { + assert.throws(() => { sharp().tiff({ predictor: 'a' }); }); }); - it('TIFF invalid resolutionUnit option throws', function () { - assert.throws(function () { + it('TIFF invalid resolutionUnit option throws', () => { + assert.throws(() => { sharp().tiff({ resolutionUnit: 'none' }); }); }); - it('TIFF horizontal predictor does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF horizontal predictor does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ predictor: 'horizontal' }); }); }); - it('TIFF float predictor does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF float predictor does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ predictor: 'float' }); }); }); - it('TIFF none predictor does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF none predictor does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ predictor: 'none' }); }); }); - it('TIFF tiled pyramid image without compression enlarges test file', function (done) { + it('TIFF tiled pyramid image without compression enlarges test file', (_t, done) => { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -450,89 +463,89 @@ describe('TIFF', function () { }); }); - it('TIFF pyramid true value does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF pyramid true value does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ pyramid: true }); }); }); - it('Invalid TIFF pyramid value throws error', function () { - assert.throws(function () { + it('Invalid TIFF pyramid value throws error', () => { + assert.throws(() => { sharp().tiff({ pyramid: 'true' }); }); }); - it('TIFF miniswhite true value does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF miniswhite true value does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ miniswhite: true }); }); }); - it('Invalid TIFF miniswhite value throws error', function () { - assert.throws(function () { + it('Invalid TIFF miniswhite value throws error', () => { + assert.throws(() => { sharp().tiff({ miniswhite: 'true' }); }); }); - it('Invalid TIFF tile value throws error', function () { - assert.throws(function () { + it('Invalid TIFF tile value throws error', () => { + assert.throws(() => { sharp().tiff({ tile: 'true' }); }); }); - it('TIFF tile true value does not throw error', function () { - assert.doesNotThrow(function () { + it('TIFF tile true value does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ tile: true }); }); }); - it('Valid TIFF tileHeight value does not throw error', function () { - assert.doesNotThrow(function () { + it('Valid TIFF tileHeight value does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ tileHeight: 512 }); }); }); - it('Valid TIFF tileWidth value does not throw error', function () { - assert.doesNotThrow(function () { + it('Valid TIFF tileWidth value does not throw error', () => { + assert.doesNotThrow(() => { sharp().tiff({ tileWidth: 512 }); }); }); - it('Invalid TIFF tileHeight value throws error', function () { - assert.throws(function () { + it('Invalid TIFF tileHeight value throws error', () => { + assert.throws(() => { sharp().tiff({ tileHeight: '256' }); }); }); - it('Invalid TIFF tileWidth value throws error', function () { - assert.throws(function () { + it('Invalid TIFF tileWidth value throws error', () => { + assert.throws(() => { sharp().tiff({ tileWidth: '256' }); }); }); - it('Invalid TIFF tileHeight value throws error', function () { - assert.throws(function () { + it('Invalid TIFF tileHeight value throws error', () => { + assert.throws(() => { sharp().tiff({ tileHeight: 0 }); }); }); - it('Invalid TIFF tileWidth value throws error', function () { - assert.throws(function () { + it('Invalid TIFF tileWidth value throws error', () => { + assert.throws(() => { sharp().tiff({ tileWidth: 0 }); }); }); - it('TIFF file input with invalid page fails gracefully', function (done) { + it('TIFF file input with invalid page fails gracefully', (_t, done) => { sharp(fixtures.inputTiffMultipage, { page: 2 }) - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, !!err); done(); }); }); - it('TIFF buffer input with invalid page fails gracefully', function (done) { + it('TIFF buffer input with invalid page fails gracefully', (_t, done) => { sharp(fs.readFileSync(fixtures.inputTiffMultipage), { page: 2 }) - .toBuffer(function (err) { + .toBuffer((err) => { assert.strictEqual(true, !!err); done(); }); diff --git a/test/unit/tile.js b/test/unit/tile.js index 53e91bf5d..8d477ebfa 100644 --- a/test/unit/tile.js +++ b/test/unit/tile.js @@ -1,11 +1,12 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); +const fs = require('node:fs'); +const path = require('node:path'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const extractZip = require('extract-zip'); @@ -13,27 +14,26 @@ const sharp = require('../../'); const fixtures = require('../fixtures'); // Verifies all tiles in a given dz output directory are <= size -const assertDeepZoomTiles = function (directory, expectedSize, expectedLevels, done) { +const assertDeepZoomTiles = (directory, expectedSize, expectedLevels, done) => { // Get levels const dirents = fs.readdirSync(directory, { withFileTypes: true }); const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name); assert.strictEqual(expectedLevels, levels.length); // Get tiles const tiles = []; - levels.forEach(function (level) { + levels.forEach((level) => { // Verify level directory name assert.strictEqual(true, /^[0-9]+$/.test(level)); - fs.readdirSync(path.join(directory, level)).forEach(function (tile) { + fs.readdirSync(path.join(directory, level)).forEach((tile) => { // Verify tile file name assert.strictEqual(true, /^[0-9]+_[0-9]+\.jpeg$/.test(tile)); tiles.push(path.join(directory, level, tile)); }); }); // Verify each tile is <= expectedSize - Promise.all(tiles.map(function (tile) { - return sharp(tile) + Promise.all(tiles.map((tile) => sharp(tile) .metadata() - .then(function (metadata) { + .then((metadata) => { assert.strictEqual('jpeg', metadata.format); assert.strictEqual('srgb', metadata.space); assert.strictEqual(3, metadata.channels); @@ -41,23 +41,22 @@ const assertDeepZoomTiles = function (directory, expectedSize, expectedLevels, d assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(true, metadata.width <= expectedSize); assert.strictEqual(true, metadata.height <= expectedSize); - }); - })) + }))) .then(() => done()) .catch(done); }; -const assertZoomifyTiles = function (directory, expectedTileSize, expectedLevels, done) { - fs.stat(path.join(directory, 'ImageProperties.xml'), function (err, stat) { +const assertZoomifyTiles = (directory, expectedLevels, done) => { + fs.stat(path.join(directory, 'ImageProperties.xml'), (err, stat) => { if (err) throw err; assert.ok(stat.isFile()); assert.ok(stat.size > 0); let maxTileLevel = -1; - fs.readdirSync(path.join(directory, 'TileGroup0')).forEach(function (tile) { + fs.readdirSync(path.join(directory, 'TileGroup0')).forEach((tile) => { // Verify tile file name assert.ok(/^[0-9]+-[0-9]+-[0-9]+\.jpg$/.test(tile)); - const level = parseInt(tile.split('-')[0]); + const level = Number(tile.split('-')[0]); maxTileLevel = Math.max(maxTileLevel, level); }); @@ -67,24 +66,24 @@ const assertZoomifyTiles = function (directory, expectedTileSize, expectedLevels }); }; -const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels, done) { +const assertGoogleTiles = (directory, expectedLevels, done) => { // Get levels const dirents = fs.readdirSync(directory, { withFileTypes: true }); const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name); assert.strictEqual(expectedLevels, levels.length); - fs.stat(path.join(directory, 'blank.png'), function (err, stat) { + fs.stat(path.join(directory, 'blank.png'), (err, stat) => { if (err) throw err; assert.ok(stat.isFile()); assert.ok(stat.size > 0); // Basic check to confirm lowest and highest level tiles exist - fs.stat(path.join(directory, '0', '0', '0.jpg'), function (err, stat) { + fs.stat(path.join(directory, '0', '0', '0.jpg'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); - fs.stat(path.join(directory, (expectedLevels - 1).toString(), '0', '0.jpg'), function (err, stat) { + fs.stat(path.join(directory, (expectedLevels - 1).toString(), '0', '0.jpg'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -95,7 +94,7 @@ const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels, }; // Verifies tiles at specified level in a given output directory are > size+overlap -const assertTileOverlap = function (directory, tileSize, done) { +const assertTileOverlap = (directory, tileSize, done) => { // Get sorted levels const dirents = fs.readdirSync(directory, { withFileTypes: true }); const levels = dirents.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name).sort((a, b) => a - b); @@ -106,7 +105,7 @@ const assertTileOverlap = function (directory, tileSize, done) { // Select a tile from the approximate center of the image const squareTile = path.join(directory, highestLevel, tiles[Math.floor(tiles.length / 2)]); - sharp(squareTile).metadata(function (err, metadata) { + sharp(squareTile).metadata((err, metadata) => { if (err) { throw err; } else { @@ -118,10 +117,10 @@ const assertTileOverlap = function (directory, tileSize, done) { }); }; -describe('Tile', function () { - it('Valid size values pass', function () { - [1, 8192].forEach(function (size) { - assert.doesNotThrow(function () { +describe('Tile', () => { + it('Valid size values pass', () => { + [1, 8192].forEach((size) => { + assert.doesNotThrow(() => { sharp().tile({ size }); @@ -129,9 +128,9 @@ describe('Tile', function () { }); }); - it('Invalid size values fail', function () { - ['zoinks', 1.1, -1, 0, 8193].forEach(function (size) { - assert.throws(function () { + it('Invalid size values fail', () => { + ['zoinks', 1.1, -1, 0, 8193].forEach((size) => { + assert.throws(() => { sharp().tile({ size }); @@ -139,9 +138,9 @@ describe('Tile', function () { }); }); - it('Valid overlap values pass', function () { - [0, 8192].forEach(function (overlap) { - assert.doesNotThrow(function () { + it('Valid overlap values pass', () => { + [0, 8192].forEach((overlap) => { + assert.doesNotThrow(() => { sharp().tile({ size: 8192, overlap @@ -150,9 +149,9 @@ describe('Tile', function () { }); }); - it('Invalid overlap values fail', function () { - ['zoinks', 1.1, -1, 8193].forEach(function (overlap) { - assert.throws(function () { + it('Invalid overlap values fail', () => { + ['zoinks', 1.1, -1, 8193].forEach((overlap) => { + assert.throws(() => { sharp().tile({ overlap }); @@ -160,9 +159,9 @@ describe('Tile', function () { }); }); - it('Valid container values pass', function () { - ['fs', 'zip'].forEach(function (container) { - assert.doesNotThrow(function () { + it('Valid container values pass', () => { + ['fs', 'zip'].forEach((container) => { + assert.doesNotThrow(() => { sharp().tile({ container }); @@ -170,9 +169,9 @@ describe('Tile', function () { }); }); - it('Invalid container values fail', function () { - ['zoinks', 1].forEach(function (container) { - assert.throws(function () { + it('Invalid container values fail', () => { + ['zoinks', 1].forEach((container) => { + assert.throws(() => { sharp().tile({ container }); @@ -180,9 +179,9 @@ describe('Tile', function () { }); }); - it('Valid layout values pass', function () { - ['dz', 'google', 'zoomify'].forEach(function (layout) { - assert.doesNotThrow(function () { + it('Valid layout values pass', () => { + ['dz', 'google', 'zoomify'].forEach((layout) => { + assert.doesNotThrow(() => { sharp().tile({ layout }); @@ -190,9 +189,9 @@ describe('Tile', function () { }); }); - it('Invalid layout values fail', function () { - ['zoinks', 1].forEach(function (layout) { - assert.throws(function () { + it('Invalid layout values fail', () => { + ['zoinks', 1].forEach((layout) => { + assert.throws(() => { sharp().tile({ layout }); @@ -200,30 +199,30 @@ describe('Tile', function () { }); }); - it('Valid formats pass', function () { - ['jpeg', 'png', 'webp'].forEach(function (format) { - assert.doesNotThrow(function () { + it('Valid formats pass', () => { + ['jpeg', 'png', 'webp'].forEach((format) => { + assert.doesNotThrow(() => { sharp().toFormat(format).tile(); }); }); }); - it('Invalid formats fail', function () { - ['tiff', 'raw'].forEach(function (format) { - assert.throws(function () { + it('Invalid formats fail', () => { + ['tiff', 'raw'].forEach((format) => { + assert.throws(() => { sharp().toFormat(format).tile(); }); }); }); - it('Valid depths pass', function () { - ['onepixel', 'onetile', 'one'].forEach(function (depth) { + it('Valid depths pass', () => { + ['onepixel', 'onetile', 'one'].forEach((depth) => { assert.doesNotThrow(() => sharp().tile({ depth })); }); }); - it('Invalid depths fail', function () { - ['depth', 1].forEach(function (depth) { + it('Invalid depths fail', () => { + ['depth', 1].forEach((depth) => { assert.throws( () => sharp().tile({ depth }), /Expected one of: onepixel, onetile, one for depth but received/ @@ -231,16 +230,16 @@ describe('Tile', function () { }); }); - it('Prevent larger overlap than default size', function () { - assert.throws(function () { + it('Prevent larger overlap than default size', () => { + assert.throws(() => { sharp().tile({ overlap: 257 }); }); }); - it('Prevent larger overlap than provided size', function () { - assert.throws(function () { + it('Prevent larger overlap than provided size', () => { + assert.throws(() => { sharp().tile({ size: 512, overlap: 513 @@ -248,9 +247,9 @@ describe('Tile', function () { }); }); - it('Valid rotation angle values pass', function () { - [90, 270, -90].forEach(function (angle) { - assert.doesNotThrow(function () { + it('Valid rotation angle values pass', () => { + [90, 270, -90].forEach((angle) => { + assert.doesNotThrow(() => { sharp().tile({ angle }); @@ -258,9 +257,9 @@ describe('Tile', function () { }); }); - it('Invalid rotation angle values fail', function () { - ['zoinks', 1.1, -1, 27].forEach(function (angle) { - assert.throws(function () { + it('Invalid rotation angle values fail', () => { + ['zoinks', 1.1, -1, 27].forEach((angle) => { + assert.throws(() => { sharp().tile({ angle }); @@ -268,9 +267,9 @@ describe('Tile', function () { }); }); - it('Valid skipBlanks threshold values pass', function () { - [-1, 0, 255, 65535].forEach(function (skipBlanksThreshold) { - assert.doesNotThrow(function () { + it('Valid skipBlanks threshold values pass', () => { + [-1, 0, 255, 65535].forEach((skipBlanksThreshold) => { + assert.doesNotThrow(() => { sharp().tile({ skipBlanks: skipBlanksThreshold }); @@ -278,9 +277,9 @@ describe('Tile', function () { }); }); - it('InvalidskipBlanks threshold values fail', function () { - ['zoinks', -2, 65536].forEach(function (skipBlanksThreshold) { - assert.throws(function () { + it('InvalidskipBlanks threshold values fail', () => { + ['zoinks', -2, 65536].forEach((skipBlanksThreshold) => { + assert.throws(() => { sharp().tile({ skipBlanks: skipBlanksThreshold }); @@ -288,42 +287,42 @@ describe('Tile', function () { }); }); - it('Valid center parameter value passes', function () { + it('Valid center parameter value passes', () => { assert.doesNotThrow( () => sharp().tile({ center: true }) ); }); - it('Invalid centre parameter value fails', function () { + it('Invalid centre parameter value fails', () => { assert.throws( () => sharp().tile({ centre: 'true' }), /Expected boolean for tileCentre but received true of type string/ ); }); - it('Valid id parameter value passes', function () { - assert.doesNotThrow(function () { + it('Valid id parameter value passes', () => { + assert.doesNotThrow(() => { sharp().tile({ id: 'test' }); }); }); - it('Invalid id parameter value fails', function () { - assert.throws(function () { + it('Invalid id parameter value fails', () => { + assert.throws(() => { sharp().tile({ id: true }); }); }); - it('Valid basename parameter value passes', function () { + it('Valid basename parameter value passes', () => { assert.doesNotThrow( () => sharp().tile({ basename: 'pass' }) ); }); - it('Invalid basename parameter value fails', function () { + it('Invalid basename parameter value fails', () => { assert.throws( () => sharp().tile({ basename: true }), /Expected string for basename but received/ @@ -331,11 +330,11 @@ describe('Tile', function () { }); if (sharp.format.dz.output.file) { - it('Deep Zoom layout', function (done) { + it('Deep Zoom layout', (_t, done) => { const directory = fixtures.path('output.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) - .toFile(fixtures.path('output.dzi'), function (err, info) { + .toFile(fixtures.path('output.dzi'), (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -347,37 +346,37 @@ describe('Tile', function () { }); }); - it('Deep Zoom layout with custom size+overlap', function (done) { + it('Deep Zoom layout with custom size+overlap', (_t, done) => { const directory = fixtures.path('output.512.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 512, overlap: 16 }) - .toFile(fixtures.path('output.512.dzi'), function (err, info) { + .toFile(fixtures.path('output.512.dzi'), (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual('undefined', typeof info.size); - assertDeepZoomTiles(directory, 512 + (2 * 16), 13, function () { + assertDeepZoomTiles(directory, 512 + (2 * 16), 13, () => { assertTileOverlap(directory, 512, done); }); }); }); }); - it('Deep Zoom layout with custom size+angle', function (done) { + it('Deep Zoom layout with custom size+angle', (_t, done) => { const directory = fixtures.path('output.512_90.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 512, angle: 90 }) - .toFile(fixtures.path('output.512_90.dzi'), function (err, info) { + .toFile(fixtures.path('output.512_90.dzi'), (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -391,7 +390,7 @@ describe('Tile', function () { // expected are w=512 and h=170 for the 0_1.jpeg. // if a 0 angle is supplied to the .tile function // the expected values are w=170 and h=512 for the 1_0.jpeg - sharp(tile).metadata(function (err, metadata) { + sharp(tile).metadata((err, metadata) => { if (err) { throw err; } else { @@ -403,15 +402,15 @@ describe('Tile', function () { }); }); - it('Deep Zoom layout with depth of one', function (done) { + it('Deep Zoom layout with depth of one', (_t, done) => { const directory = fixtures.path('output.512_depth_one.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 512, depth: 'one' }) - .toFile(fixtures.path('output.512_depth_one.dzi'), function (err, info) { + .toFile(fixtures.path('output.512_depth_one.dzi'), (err) => { if (err) throw err; // Verify only one depth generated assertDeepZoomTiles(directory, 512, 1, done); @@ -419,15 +418,15 @@ describe('Tile', function () { }); }); - it('Deep Zoom layout with depth of onepixel', function (done) { + it('Deep Zoom layout with depth of onepixel', (_t, done) => { const directory = fixtures.path('output.512_depth_onepixel.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 512, depth: 'onepixel' }) - .toFile(fixtures.path('output.512_depth_onepixel.dzi'), function (err, info) { + .toFile(fixtures.path('output.512_depth_onepixel.dzi'), (err) => { if (err) throw err; // Verify only one depth generated assertDeepZoomTiles(directory, 512, 13, done); @@ -435,15 +434,15 @@ describe('Tile', function () { }); }); - it('Deep Zoom layout with depth of onetile', function (done) { + it('Deep Zoom layout with depth of onetile', (_t, done) => { const directory = fixtures.path('output.256_depth_onetile.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 256, depth: 'onetile' }) - .toFile(fixtures.path('output.256_depth_onetile.dzi'), function (err, info) { + .toFile(fixtures.path('output.256_depth_onetile.dzi'), (err) => { if (err) throw err; // Verify only one depth generated assertDeepZoomTiles(directory, 256, 5, done); @@ -451,15 +450,15 @@ describe('Tile', function () { }); }); - it('Deep Zoom layout with skipBlanks', function (done) { + it('Deep Zoom layout with skipBlanks', (_t, done) => { const directory = fixtures.path('output.256_skip_blanks.dzi_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpgOverlayLayer2) .tile({ size: 256, skipBlanks: 0 }) - .toFile(fixtures.path('output.256_skip_blanks.dzi'), function (err, info) { + .toFile(fixtures.path('output.256_skip_blanks.dzi'), (err) => { if (err) throw err; // assert them 0_0.jpeg doesn't exist because it's a white tile const whiteTilePath = path.join(directory, '11', '0_0.jpeg'); @@ -470,21 +469,21 @@ describe('Tile', function () { }); }); - it('Zoomify layout', function (done) { + it('Zoomify layout', (_t, done) => { const directory = fixtures.path('output.zoomify.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ layout: 'zoomify' }) - .toFile(fixtures.path('output.zoomify.dzi'), function (err, info) { + .toFile(fixtures.path('output.zoomify.dzi'), (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - fs.stat(path.join(directory, 'ImageProperties.xml'), function (err, stat) { + fs.stat(path.join(directory, 'ImageProperties.xml'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -494,79 +493,79 @@ describe('Tile', function () { }); }); - it('Zoomify layout with depth one', function (done) { + it('Zoomify layout with depth one', (_t, done) => { const directory = fixtures.path('output.zoomify.depth_one.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 256, layout: 'zoomify', depth: 'one' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertZoomifyTiles(directory, 256, 1, done); + assertZoomifyTiles(directory, 1, done); }); }); }); - it('Zoomify layout with depth onetile', function (done) { + it('Zoomify layout with depth onetile', (_t, done) => { const directory = fixtures.path('output.zoomify.depth_onetile.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 256, layout: 'zoomify', depth: 'onetile' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertZoomifyTiles(directory, 256, 5, done); + assertZoomifyTiles(directory, 5, done); }); }); }); - it('Zoomify layout with depth onepixel', function (done) { + it('Zoomify layout with depth onepixel', (_t, done) => { const directory = fixtures.path('output.zoomify.depth_onepixel.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ size: 256, layout: 'zoomify', depth: 'onepixel' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertZoomifyTiles(directory, 256, 13, done); + assertZoomifyTiles(directory, 13, done); }); }); }); - it('Zoomify layout with skip blanks', function (done) { + it('Zoomify layout with skip blanks', (_t, done) => { const directory = fixtures.path('output.zoomify.skipBlanks.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpgOverlayLayer2) .tile({ size: 256, layout: 'zoomify', skipBlanks: 0 }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; // assert them 0_0.jpeg doesn't exist because it's a white tile const whiteTilePath = path.join(directory, 'TileGroup0', '2-0-0.jpg'); @@ -576,26 +575,26 @@ describe('Tile', function () { assert.strictEqual(1536, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertZoomifyTiles(directory, 256, 4, done); + assertZoomifyTiles(directory, 4, done); }); }); }); - it('Google layout', function (done) { + it('Google layout', (_t, done) => { const directory = fixtures.path('output.google.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - fs.stat(path.join(directory, '0', '0', '0.jpg'), function (err, stat) { + fs.stat(path.join(directory, '0', '0', '0.jpg'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -605,9 +604,9 @@ describe('Tile', function () { }); }); - it('Google layout with jpeg format', function (done) { + it('Google layout with jpeg format', (_t, done) => { const directory = fixtures.path('output.jpg.google.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .jpeg({ quality: 1 @@ -615,7 +614,7 @@ describe('Tile', function () { .tile({ layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -623,7 +622,7 @@ describe('Tile', function () { assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); const sample = path.join(directory, '0', '0', '0.jpg'); - sharp(sample).metadata(function (err, metadata) { + sharp(sample).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('jpeg', metadata.format); assert.strictEqual('srgb', metadata.space); @@ -632,7 +631,7 @@ describe('Tile', function () { assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(256, metadata.width); assert.strictEqual(256, metadata.height); - fs.stat(sample, function (err, stat) { + fs.stat(sample, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.size < 2000); done(); @@ -642,9 +641,9 @@ describe('Tile', function () { }); }); - it('Google layout with png format', function (done) { + it('Google layout with png format', (_t, done) => { const directory = fixtures.path('output.png.google.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .png({ compressionLevel: 0 @@ -652,7 +651,7 @@ describe('Tile', function () { .tile({ layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -660,7 +659,7 @@ describe('Tile', function () { assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); const sample = path.join(directory, '0', '0', '0.png'); - sharp(sample).metadata(function (err, metadata) { + sharp(sample).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('png', metadata.format); assert.strictEqual('srgb', metadata.space); @@ -669,7 +668,7 @@ describe('Tile', function () { assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(256, metadata.width); assert.strictEqual(256, metadata.height); - fs.stat(sample, function (err, stat) { + fs.stat(sample, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.size > 44000); done(); @@ -679,9 +678,9 @@ describe('Tile', function () { }); }); - it('Google layout with webp format', function (done) { + it('Google layout with webp format', (_t, done) => { const directory = fixtures.path('output.webp.google.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .webp({ quality: 1, @@ -690,7 +689,7 @@ describe('Tile', function () { .tile({ layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -698,7 +697,7 @@ describe('Tile', function () { assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); const sample = path.join(directory, '0', '0', '0.webp'); - sharp(sample).metadata(function (err, metadata) { + sharp(sample).metadata((err, metadata) => { if (err) throw err; assert.strictEqual('webp', metadata.format); assert.strictEqual('srgb', metadata.space); @@ -707,7 +706,7 @@ describe('Tile', function () { assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(256, metadata.width); assert.strictEqual(256, metadata.height); - fs.stat(sample, function (err, stat) { + fs.stat(sample, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.size < 2000); done(); @@ -717,57 +716,57 @@ describe('Tile', function () { }); }); - it('Google layout with depth one', function (done) { + it('Google layout with depth one', (_t, done) => { const directory = fixtures.path('output.google_depth_one.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ layout: 'google', depth: 'one', size: 256 }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertGoogleTiles(directory, 256, 1, done); + assertGoogleTiles(directory, 1, done); }); }); }); - it('Google layout with depth onetile', function (done) { + it('Google layout with depth onetile', (_t, done) => { const directory = fixtures.path('output.google_depth_onetile.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ layout: 'google', depth: 'onetile', size: 256 }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertGoogleTiles(directory, 256, 5, done); + assertGoogleTiles(directory, 5, done); }); }); }); - it('Google layout with default skip Blanks', function (done) { + it('Google layout with default skip Blanks', (_t, done) => { const directory = fixtures.path('output.google_depth_skipBlanks.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputPng) .tile({ layout: 'google', size: 256 }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; const whiteTilePath = path.join(directory, '4', '8', '0.jpg'); @@ -778,20 +777,20 @@ describe('Tile', function () { assert.strictEqual(2074, info.height); assert.strictEqual(3, info.channels); assert.strictEqual(undefined, info.size); - assertGoogleTiles(directory, 256, 5, done); + assertGoogleTiles(directory, 5, done); }); }); }); - it('Google layout with center image in tile', function (done) { + it('Google layout with center image in tile', (_t, done) => { const directory = fixtures.path('output.google_center.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ center: true, layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -803,15 +802,15 @@ describe('Tile', function () { }); }); - it('Google layout with center image in tile centre', function (done) { + it('Google layout with center image in tile centre', (_t, done) => { const directory = fixtures.path('output.google_center.dzi'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ centre: true, layout: 'google' }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -823,17 +822,17 @@ describe('Tile', function () { }); }); - it('IIIFv2 layout', function (done) { + it('IIIFv2 layout', (_t, done) => { const name = 'output.iiif.info'; const directory = fixtures.path(name); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { const id = 'https://sharp.test.com/iiif'; sharp(fixtures.inputJpg) .tile({ layout: 'iiif', id }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -843,7 +842,7 @@ describe('Tile', function () { const infoJson = require(path.join(directory, 'info.json')); assert.strictEqual('http://iiif.io/api/image/2/context.json', infoJson['@context']); assert.strictEqual(`${id}/${name}`, infoJson['@id']); - fs.stat(path.join(directory, '0,0,256,256', '256,', '0', 'default.jpg'), function (err, stat) { + fs.stat(path.join(directory, '0,0,256,256', '256,', '0', 'default.jpg'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -853,17 +852,17 @@ describe('Tile', function () { }); }); - it('IIIFv3 layout', function (done) { + it('IIIFv3 layout', (_t, done) => { const name = 'output.iiif3.info'; const directory = fixtures.path(name); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { const id = 'https://sharp.test.com/iiif3'; sharp(fixtures.inputJpg) .tile({ layout: 'iiif3', id }) - .toFile(directory, function (err, info) { + .toFile(directory, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -874,7 +873,7 @@ describe('Tile', function () { assert.strictEqual('http://iiif.io/api/image/3/context.json', infoJson['@context']); assert.strictEqual('ImageService3', infoJson.type); assert.strictEqual(`${id}/${name}`, infoJson.id); - fs.stat(path.join(directory, '0,0,256,256', '256,256', '0', 'default.jpg'), function (err, stat) { + fs.stat(path.join(directory, '0,0,256,256', '256,256', '0', 'default.jpg'), (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -884,20 +883,20 @@ describe('Tile', function () { }); }); - it('Write to ZIP container using file extension', function (done) { + it('Write to ZIP container using file extension', (_t, done) => { const container = fixtures.path('output.dz.container.zip'); const extractTo = fixtures.path('output.dz.container'); const directory = path.join(extractTo, 'output.dz.container_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) - .toFile(container, function (err, info) { + .toFile(container, (err, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual('number', typeof info.size); - fs.stat(container, function (err, stat) { + fs.stat(container, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -905,22 +904,22 @@ describe('Tile', function () { .then(() => { assertDeepZoomTiles(directory, 256, 13, done); }) - .catch(done); + .catch(_t, done); }); }); }); }); - it('Write to ZIP container using container tile option', function (done) { + it('Write to ZIP container using container tile option', (_t, done) => { const container = fixtures.path('output.dz.containeropt.zip'); const extractTo = fixtures.path('output.dz.containeropt'); const directory = path.join(extractTo, 'output.dz.containeropt_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ container: 'zip' }) - .toFile(container, function (err, info) { + .toFile(container, (err, info) => { // Vips overrides .dzi extension to .zip used by container var below if (err) throw err; assert.strictEqual('dz', info.format); @@ -928,7 +927,7 @@ describe('Tile', function () { assert.strictEqual(2225, info.height); assert.strictEqual(3, info.channels); assert.strictEqual('number', typeof info.size); - fs.stat(container, function (err, stat) { + fs.stat(container, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -936,20 +935,20 @@ describe('Tile', function () { .then(() => { assertDeepZoomTiles(directory, 256, 13, done); }) - .catch(done); + .catch(_t, done); }); }); }); }); - it('Write ZIP container to Buffer', function (done) { + it('Write ZIP container to Buffer', (_t, done) => { const container = fixtures.path('output.dz.tiles.zip'); const extractTo = fixtures.path('output.dz.tiles'); const directory = path.join(extractTo, 'output.dz.tiles_files'); - fs.rm(directory, { recursive: true }, function () { + fs.rm(directory, { recursive: true }, () => { sharp(fixtures.inputJpg) .tile({ basename: 'output.dz.tiles' }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('dz', info.format); assert.strictEqual(2725, info.width); @@ -957,7 +956,7 @@ describe('Tile', function () { assert.strictEqual(3, info.channels); assert.strictEqual('number', typeof info.size); fs.writeFileSync(container, data); - fs.stat(container, function (err, stat) { + fs.stat(container, (err, stat) => { if (err) throw err; assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.size > 0); @@ -965,7 +964,7 @@ describe('Tile', function () { .then(() => { assertDeepZoomTiles(directory, 256, 13, done); }) - .catch(done); + .catch(_t, done); }); }); }); diff --git a/test/unit/timeout.js b/test/unit/timeout.js index b80ef263e..77aad8fa6 100644 --- a/test/unit/timeout.js +++ b/test/unit/timeout.js @@ -1,14 +1,15 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Timeout', function () { +describe('Timeout', () => { it('Will timeout after 1s when performing slow blur operation', () => assert.rejects( () => sharp(fixtures.inputJpg) .blur(200) diff --git a/test/unit/tint.js b/test/unit/tint.js index 19f4ae2db..db3f4984f 100644 --- a/test/unit/tint.js +++ b/test/unit/tint.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); @@ -11,13 +12,13 @@ const fixtures = require('../fixtures'); // Allow for small rounding differences between platforms const maxDistance = 6; -describe('Tint', function () { - it('tints rgb image red', function (done) { +describe('Tint', () => { + it('tints rgb image red', (_t, done) => { const output = fixtures.path('output.tint-red.jpg'); sharp(fixtures.inputJpg) .resize(320, 240) .tint('#FF0000') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), maxDistance); @@ -25,12 +26,12 @@ describe('Tint', function () { }); }); - it('tints rgb image green', function (done) { + it('tints rgb image green', (_t, done) => { const output = fixtures.path('output.tint-green.jpg'); sharp(fixtures.inputJpg) .resize(320, 240) .tint('#00FF00') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), maxDistance); @@ -38,12 +39,12 @@ describe('Tint', function () { }); }); - it('tints rgb image blue', function (done) { + it('tints rgb image blue', (_t, done) => { const output = fixtures.path('output.tint-blue.jpg'); sharp(fixtures.inputJpg) .resize(320, 240) .tint('#0000FF') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), maxDistance); @@ -51,12 +52,12 @@ describe('Tint', function () { }); }); - it('tints rgb image with sepia tone', function (done) { + it('tints rgb image with sepia tone', (_t, done) => { const output = fixtures.path('output.tint-sepia-hex.jpg'); sharp(fixtures.inputJpg) .resize(320, 240) .tint('#704214') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -65,12 +66,12 @@ describe('Tint', function () { }); }); - it('tints rgb image with sepia tone with rgb colour', function (done) { + it('tints rgb image with sepia tone with rgb colour', (_t, done) => { const output = fixtures.path('output.tint-sepia-rgb.jpg'); sharp(fixtures.inputJpg) .resize(320, 240) .tint([112, 66, 20]) - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -79,12 +80,12 @@ describe('Tint', function () { }); }); - it('tints rgb image with alpha channel', function (done) { + it('tints rgb image with alpha channel', (_t, done) => { const output = fixtures.path('output.tint-alpha.png'); sharp(fixtures.inputPngRGBWithAlpha) .resize(320, 240) .tint('#704214') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); @@ -93,12 +94,12 @@ describe('Tint', function () { }); }); - it('tints cmyk image red', function (done) { + it('tints cmyk image red', (_t, done) => { const output = fixtures.path('output.tint-cmyk.jpg'); sharp(fixtures.inputJpgWithCmykProfile) .resize(320, 240) .tint('#FF0000') - .toFile(output, function (err, info) { + .toFile(output, (err, info) => { if (err) throw err; assert.strictEqual(true, info.size > 0); fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), maxDistance); diff --git a/test/unit/toBuffer.js b/test/unit/toBuffer.js index 4db428b93..7e4b1d452 100644 --- a/test/unit/toBuffer.js +++ b/test/unit/toBuffer.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); diff --git a/test/unit/toFormat.js b/test/unit/toFormat.js index a48742db2..0974e4604 100644 --- a/test/unit/toFormat.js +++ b/test/unit/toFormat.js @@ -1,9 +1,10 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); diff --git a/test/unit/trim.js b/test/unit/trim.js index aa559d5a8..99b14a8d9 100644 --- a/test/unit/trim.js +++ b/test/unit/trim.js @@ -1,16 +1,17 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const inRange = require('../../lib/is').inRange; const fixtures = require('../fixtures'); -describe('Trim borders', function () { - it('Skip shrink-on-load', function (done) { +describe('Trim borders', () => { + it('Skip shrink-on-load', (_t, done) => { const expected = fixtures.expected('alpha-layer-2-trim-resize.jpg'); sharp(fixtures.inputJpgOverlayLayer2) .trim() @@ -18,7 +19,7 @@ describe('Trim borders', function () { width: 300, fastShrinkOnLoad: false }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(300, info.width); @@ -43,13 +44,13 @@ describe('Trim borders', function () { }) ); - it('16-bit PNG with alpha channel', function (done) { + it('16-bit PNG with alpha channel', (_t, done) => { sharp(fixtures.inputPngWithTransparency16bit) .resize(32, 32) .trim({ threshold: 20 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('png', info.format); @@ -62,7 +63,7 @@ describe('Trim borders', function () { }); }); - it('Attempt to trim 2x2 pixel image fails', function (done) { + it('Attempt to trim 2x2 pixel image fails', (_t, done) => { sharp({ create: { width: 2, @@ -80,7 +81,7 @@ describe('Trim borders', function () { assert.strictEqual('Image to trim must be at least 3x3 pixels', err.message); done(); }) - .catch(done); + .catch(_t, done); }); it('Should rotate before trim', () => @@ -210,7 +211,7 @@ describe('Trim borders', function () { assert.strictEqual(info.trimOffsetTop, -552); }); - describe('Invalid parameters', function () { + describe('Invalid parameters', () => { Object.entries({ 'Invalid string': 'fail', 'Invalid background option': { @@ -222,16 +223,16 @@ describe('Trim borders', function () { 'Invalid lineArt': { lineArt: 'fail' } - }).forEach(function ([description, parameter]) { - it(description, function () { - assert.throws(function () { + }).forEach(([description, parameter]) => { + it(description, () => { + assert.throws(() => { sharp().trim(parameter); }); }); }); }); - describe('Specific background colour', function () { + describe('Specific background colour', () => { it('Doesn\'t trim at all', async () => { const { info } = await sharp(fixtures.inputPngTrimSpecificColour) .trim({ diff --git a/test/unit/unflatten.js b/test/unit/unflatten.js index 68147b240..1db60af33 100644 --- a/test/unit/unflatten.js +++ b/test/unit/unflatten.js @@ -1,29 +1,30 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 - -'use strict'; +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ +const { describe, it } = require('node:test'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Unflatten', function () { - it('unflatten white background', function (done) { +describe('Unflatten', () => { + it('unflatten white background', (_t, done) => { sharp(fixtures.inputPng).unflatten() - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('unflatten-white-transparent.png'), data, { threshold: 0 }, done); }); }); - it('unflatten transparent image', function (done) { + it('unflatten transparent image', (_t, done) => { sharp(fixtures.inputPngTrimSpecificColourIncludeAlpha).unflatten() - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('unflatten-flag-white-transparent.png'), data, { threshold: 0 }, done); }); }); - it('unflatten using threshold', function (done) { + it('unflatten using threshold', (_t, done) => { sharp(fixtures.inputPngPalette).unflatten().threshold(128, { grayscale: false }) - .toBuffer(function (err, data) { + .toBuffer((err, data) => { if (err) throw err; fixtures.assertSimilar(fixtures.expected('unflatten-swiss.png'), data, { threshold: 1 }, done); }); diff --git a/test/unit/util.js b/test/unit/util.js index 9d5b9c620..e3676db6d 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -1,35 +1,38 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const assert = require('assert'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const semver = require('semver'); const sharp = require('../../'); -describe('Utilities', function () { - describe('Cache', function () { - it('Can be disabled', function (done) { - queueMicrotask(() => { - sharp.cache(false); +describe('Utilities', () => { + describe('Cache', () => { + it('Can be disabled', (_t, done) => { + const check = setInterval(() => { const cache = sharp.cache(false); - assert.strictEqual(cache.memory.current, 0); - assert.strictEqual(cache.memory.max, 0); - assert.strictEqual(typeof cache.memory.high, 'number'); - assert.strictEqual(cache.files.current, 0); - assert.strictEqual(cache.files.max, 0); - assert.strictEqual(cache.items.current, 0); - assert.strictEqual(cache.items.max, 0); - done(); - }); - }); - it('Can be enabled with defaults', function () { + const empty = + cache.memory.current + + cache.memory.max + + cache.files.current + + cache.files.max + + cache.items.current + + cache.items.max === 0; + if (empty) { + clearInterval(check); + done(); + } + }, 2000); + }); + it('Can be enabled with defaults', () => { const cache = sharp.cache(true); assert.strictEqual(cache.memory.max, 50); assert.strictEqual(cache.files.max, 20); assert.strictEqual(cache.items.max, 100); }); - it('Can be set to zero', function () { + it('Can be set to zero', () => { const cache = sharp.cache({ memory: 0, files: 0, @@ -39,7 +42,7 @@ describe('Utilities', function () { assert.strictEqual(cache.files.max, 0); assert.strictEqual(cache.items.max, 0); }); - it('Can be set to a maximum of 10MB, 100 files and 1000 items', function () { + it('Can be set to a maximum of 10MB, 100 files and 1000 items', () => { const cache = sharp.cache({ memory: 10, files: 100, @@ -49,7 +52,7 @@ describe('Utilities', function () { assert.strictEqual(cache.files.max, 100); assert.strictEqual(cache.items.max, 1000); }); - it('Ignores invalid values', function () { + it('Ignores invalid values', () => { sharp.cache(true); const cache = sharp.cache('spoons'); assert.strictEqual(cache.memory.max, 50); @@ -58,24 +61,24 @@ describe('Utilities', function () { }); }); - describe('Concurrency', function () { - it('Can be set to use 16 threads', function () { + describe('Concurrency', () => { + it('Can be set to use 16 threads', () => { sharp.concurrency(16); assert.strictEqual(16, sharp.concurrency()); }); - it('Can be reset to default', function () { + it('Can be reset to default', () => { sharp.concurrency(0); assert.strictEqual(true, sharp.concurrency() > 0); }); - it('Ignores invalid values', function () { + it('Ignores invalid values', () => { const defaultConcurrency = sharp.concurrency(); sharp.concurrency('spoons'); assert.strictEqual(defaultConcurrency, sharp.concurrency()); }); }); - describe('Counters', function () { - it('Have zero value at rest', (done) => { + describe('Counters', () => { + it('Have zero value at rest', (_t, done) => { queueMicrotask(() => { const counters = sharp.counters(); assert.strictEqual(0, counters.queue); @@ -85,28 +88,28 @@ describe('Utilities', function () { }); }); - describe('SIMD', function () { - it('Can get current state', function () { + describe('SIMD', () => { + it('Can get current state', () => { const simd = sharp.simd(); assert.strictEqual(typeof simd, 'boolean'); }); - it('Can disable', function () { + it('Can disable', () => { const simd = sharp.simd(false); assert.strictEqual(simd, false); }); - it('Can attempt to enable', function () { + it('Can attempt to enable', () => { const simd = sharp.simd(true); assert.strictEqual(typeof simd, 'boolean'); }); }); - describe('Format', function () { - it('Contains expected attributes', function () { + describe('Format', () => { + it('Contains expected attributes', () => { assert.strictEqual('object', typeof sharp.format); - Object.keys(sharp.format).forEach(function (format) { + Object.keys(sharp.format).forEach((format) => { assert.strictEqual(true, 'id' in sharp.format[format]); assert.strictEqual(format, sharp.format[format].id); - ['input', 'output'].forEach(function (direction) { + ['input', 'output'].forEach((direction) => { assert.strictEqual(true, direction in sharp.format[format]); assert.strictEqual('object', typeof sharp.format[format][direction]); assert.strictEqual(true, [3, 4].includes(Object.keys(sharp.format[format][direction]).length)); @@ -119,30 +122,30 @@ describe('Utilities', function () { }); }); }); - it('Raw file=false, buffer=true, stream=true', function () { - ['input', 'output'].forEach(function (direction) { + it('Raw file=false, buffer=true, stream=true', () => { + ['input', 'output'].forEach((direction) => { assert.strictEqual(false, sharp.format.raw[direction].file); assert.strictEqual(true, sharp.format.raw[direction].buffer); assert.strictEqual(true, sharp.format.raw[direction].stream); }); }); - it('vips format supports filesystem only', function () { - ['input', 'output'].forEach(function (direction) { + it('vips format supports filesystem only', () => { + ['input', 'output'].forEach((direction) => { assert.strictEqual(true, sharp.format.vips[direction].file); assert.strictEqual(false, sharp.format.vips[direction].buffer); assert.strictEqual(false, sharp.format.vips[direction].stream); }); }); - it('input fileSuffix', function () { + it('input fileSuffix', () => { assert.deepStrictEqual(['.jpg', '.jpeg', '.jpe', '.jfif'], sharp.format.jpeg.input.fileSuffix); }); - it('output alias', function () { + it('output alias', () => { assert.deepStrictEqual(['jpe', 'jpg'], sharp.format.jpeg.output.alias); }); }); - describe('Versions', function () { - it('Contains expected attributes', function () { + describe('Versions', () => { + it('Contains expected attributes', () => { assert.strictEqual('object', typeof sharp.versions); assert(semver.valid(sharp.versions.vips)); assert(semver.valid(sharp.versions.sharp)); diff --git a/test/unit/webp.js b/test/unit/webp.js index c9aae0dd1..22d37a522 100644 --- a/test/unit/webp.js +++ b/test/unit/webp.js @@ -1,20 +1,21 @@ -// Copyright 2013 Lovell Fuller and others. -// SPDX-License-Identifier: Apache-2.0 +/*! + Copyright 2013 Lovell Fuller and others. + SPDX-License-Identifier: Apache-2.0 +*/ -'use strict'; - -const fs = require('fs'); -const assert = require('assert'); +const fs = require('node:fs'); +const { describe, it } = require('node:test'); +const assert = require('node:assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('WebP', function () { - it('WebP output', function (done) { +describe('WebP', () => { + it('WebP output', (_t, done) => { sharp(fixtures.inputJpg) .resize(320, 240) .toFormat(sharp.format.webp) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -24,22 +25,22 @@ describe('WebP', function () { }); }); - it('Invalid WebP quality throws error', function () { - assert.throws(function () { + it('Invalid WebP quality throws error', () => { + assert.throws(() => { sharp().webp({ quality: 101 }); }); }); - it('Invalid WebP alpha quality throws error', function () { - assert.throws(function () { + it('Invalid WebP alpha quality throws error', () => { + assert.throws(() => { sharp().webp({ alphaQuality: 101 }); }); }); - it('should work for webp alpha quality', function (done) { + it('should work for webp alpha quality', (_t, done) => { sharp(fixtures.inputPngAlphaPremultiplicationSmall) .webp({ alphaQuality: 80, effort: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -47,10 +48,10 @@ describe('WebP', function () { }); }); - it('should work for webp lossless', function (done) { + it('should work for webp lossless', (_t, done) => { sharp(fixtures.inputPngAlphaPremultiplicationSmall) .webp({ lossless: true, effort: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -58,10 +59,10 @@ describe('WebP', function () { }); }); - it('should work for webp near-lossless', function (done) { + it('should work for webp near-lossless', (_t, done) => { sharp(fixtures.inputPngAlphaPremultiplicationSmall) .webp({ nearLossless: true, quality: 50, effort: 0 }) - .toBuffer(function (err50, data50, info50) { + .toBuffer((err50, data50, info50) => { if (err50) throw err50; assert.strictEqual(true, data50.length > 0); assert.strictEqual('webp', info50.format); @@ -69,10 +70,10 @@ describe('WebP', function () { }); }); - it('should use near-lossless when both lossless and nearLossless are specified', function (done) { + it('should use near-lossless when both lossless and nearLossless are specified', (_t, done) => { sharp(fixtures.inputPngAlphaPremultiplicationSmall) .webp({ nearLossless: true, quality: 50, lossless: true, effort: 0 }) - .toBuffer(function (err50, data50, info50) { + .toBuffer((err50, data50, info50) => { if (err50) throw err50; assert.strictEqual(true, data50.length > 0); assert.strictEqual('webp', info50.format); @@ -271,11 +272,11 @@ describe('WebP', function () { assert.deepStrictEqual(updated.delay, [120, 120, 90, 120, 120, 90, 120, 90, 30]); }); - it('should work with streams when only animated is set', function (done) { + it('should work with streams when only animated is set', (_t, done) => { fs.createReadStream(fixtures.inputWebPAnimated) .pipe(sharp({ animated: true })) .webp({ lossless: true, effort: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format); @@ -283,11 +284,11 @@ describe('WebP', function () { }); }); - it('should work with streams when only pages is set', function (done) { + it('should work with streams when only pages is set', (_t, done) => { fs.createReadStream(fixtures.inputWebPAnimated) .pipe(sharp({ pages: -1 })) .webp({ lossless: true, effort: 0 }) - .toBuffer(function (err, data, info) { + .toBuffer((err, data, info) => { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('webp', info.format);