Compare commits
53 Commits
v0.26.0-be
...
v0.27.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2a0b8c0f0 | ||
|
|
4debc46d0e | ||
|
|
f4e259d10f | ||
|
|
774d78228e | ||
|
|
0e62bde5c3 | ||
|
|
2bbd9b23e6 | ||
|
|
02676140e8 | ||
|
|
182beaa4a1 | ||
|
|
7c08a09529 | ||
|
|
ef964b5472 | ||
|
|
ee54ce9913 | ||
|
|
e59e146887 | ||
|
|
103ec0d58f | ||
|
|
a0d89ed514 | ||
|
|
c10888e6fe | ||
|
|
93455f8eb5 | ||
|
|
65acd96c8d | ||
|
|
fabe720b9b | ||
|
|
53dd313e97 | ||
|
|
2678d7a660 | ||
|
|
46718102c6 | ||
|
|
0f473fe3b1 | ||
|
|
2872602c9e | ||
|
|
ab653cae33 | ||
|
|
e6a035e575 | ||
|
|
fbe48d75dd | ||
|
|
20ba0f49dd | ||
|
|
c213e9878d | ||
|
|
9704ca4c18 | ||
|
|
49dce6219e | ||
|
|
260ff6c94f | ||
|
|
3ec281d104 | ||
|
|
c4c43d525b | ||
|
|
6c5cde363a | ||
|
|
d46b4d950f | ||
|
|
b369c4bb8a | ||
|
|
c3898487c4 | ||
|
|
ca3c5b400f | ||
|
|
97772b176c | ||
|
|
08a2965f1c | ||
|
|
76dcddfa3d | ||
|
|
79f476ae4d | ||
|
|
d406cb619c | ||
|
|
4f3890f1e4 | ||
|
|
7a9d58cc51 | ||
|
|
eef87da0e1 | ||
|
|
00e65f6f14 | ||
|
|
866e9824d1 | ||
|
|
482e6078e2 | ||
|
|
bc7ab296ef | ||
|
|
a5f4f53b56 | ||
|
|
b1227f526d | ||
|
|
78b42c8306 |
@@ -2,6 +2,7 @@ freebsd_instance:
|
|||||||
image_family: freebsd-13-0-snap
|
image_family: freebsd-13-0-snap
|
||||||
|
|
||||||
task:
|
task:
|
||||||
|
name: FreeBSD 13.0
|
||||||
env:
|
env:
|
||||||
IGNORE_OSVERSION: yes
|
IGNORE_OSVERSION: yes
|
||||||
prerequisites_script:
|
prerequisites_script:
|
||||||
@@ -9,6 +10,6 @@ task:
|
|||||||
- pkg upgrade -y
|
- pkg upgrade -y
|
||||||
- pkg install -y pkgconf vips node npm
|
- pkg install -y pkgconf vips node npm
|
||||||
install_script:
|
install_script:
|
||||||
- npm install --unsafe-perm
|
- npm install --build-from-source --unsafe-perm
|
||||||
test_script:
|
test_script:
|
||||||
- npm test
|
- npm test
|
||||||
|
|||||||
4
.github/CONTRIBUTING.md
vendored
@@ -42,10 +42,6 @@ You deserve to add your details to the [list of contributors](https://github.com
|
|||||||
|
|
||||||
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
||||||
|
|
||||||
| Release | WIP branch |
|
|
||||||
| ------: | :--------- |
|
|
||||||
| v0.26.0 | zoom |
|
|
||||||
|
|
||||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||||
|
|
||||||
### Add a new public method
|
### Add a new public method
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Documentation
|
||||||
|
url: https://sharp.pixelplumbing.com/
|
||||||
|
about: Installation instructions, complete API documentation with examples, changelog
|
||||||
2
.github/ISSUE_TEMPLATE/installation.md
vendored
@@ -7,7 +7,7 @@ labels: installation
|
|||||||
|
|
||||||
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
|
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
|
||||||
|
|
||||||
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime?
|
Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
|
||||||
|
|
||||||
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
|
||||||
|
|
||||||
|
|||||||
85
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
jobs:
|
||||||
|
CI:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
container: ${{ matrix.container }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: centos:7
|
||||||
|
nodejs_version: 10
|
||||||
|
coverage: true
|
||||||
|
prebuild: true
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: centos:7
|
||||||
|
nodejs_version: 12
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: centos:7
|
||||||
|
nodejs_version: 14
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: centos:7
|
||||||
|
nodejs_version: 15
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: node:10-alpine3.11
|
||||||
|
prebuild: true
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: node:12-alpine3.11
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: node:14-alpine3.11
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
container: node:15-alpine3.11
|
||||||
|
- os: macos-10.15
|
||||||
|
nodejs_version: 10
|
||||||
|
prebuild: true
|
||||||
|
- os: macos-10.15
|
||||||
|
nodejs_version: 12
|
||||||
|
- os: macos-10.15
|
||||||
|
nodejs_version: 14
|
||||||
|
- os: macos-10.15
|
||||||
|
nodejs_version: 15
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 10
|
||||||
|
prebuild: true
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 12
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 14
|
||||||
|
- os: windows-2019
|
||||||
|
nodejs_version: 15
|
||||||
|
steps:
|
||||||
|
- name: Dependencies (Linux glibc)
|
||||||
|
if: contains(matrix.container, 'centos')
|
||||||
|
run: |
|
||||||
|
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
|
||||||
|
yum install -y gcc-c++ make git python3 nodejs
|
||||||
|
- name: Dependencies (Linux musl)
|
||||||
|
if: contains(matrix.container, 'alpine')
|
||||||
|
run: apk add build-base git python3 --update-cache
|
||||||
|
- name: Dependencies (macOS, Windows)
|
||||||
|
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.nodejs_version }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Fix working directory ownership
|
||||||
|
if: matrix.container
|
||||||
|
run: chown root.root .
|
||||||
|
- name: Install
|
||||||
|
run: npm install --build-from-source --unsafe-perm
|
||||||
|
- name: Test
|
||||||
|
run: npm test
|
||||||
|
- name: Coverage
|
||||||
|
if: matrix.coverage
|
||||||
|
uses: coverallsapp/github-action@v1.1.2
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Prebuild
|
||||||
|
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
|
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: npx prebuild --runtime napi --target 3
|
||||||
127
.travis.yml
@@ -1,81 +1,19 @@
|
|||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
|
|
||||||
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_10.x | bash -"
|
|
||||||
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
|
|
||||||
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp bash -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 12"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
|
|
||||||
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_12.x | bash -"
|
|
||||||
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
|
|
||||||
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp bash -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 14"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
|
|
||||||
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_14.x | bash -"
|
|
||||||
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
|
|
||||||
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp bash -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux x64 (Alpine 3.9, musl 1.1.20) - Node.js 10"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
|
|
||||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
|
|
||||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 14"
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
language: shell
|
|
||||||
before_install:
|
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14.0-alpine
|
|
||||||
- sudo docker exec sharp apk add build-base git python2 --update-cache
|
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
|
||||||
|
|
||||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
|
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
|
||||||
arch: arm64
|
arch: arm64
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
language: shell
|
language: shell
|
||||||
before_install:
|
before_install:
|
||||||
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
- 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 python2 curl"
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x buster main' >/etc/apt/sources.list.d/nodesource.list"
|
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||||
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
script: sudo docker exec sharp sh -c "npm test"
|
||||||
|
after_success: "[[ -n $TRAVIS_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 3\""
|
||||||
|
|
||||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
|
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
|
||||||
arch: arm64
|
arch: arm64
|
||||||
@@ -83,12 +21,12 @@ jobs:
|
|||||||
dist: bionic
|
dist: bionic
|
||||||
language: shell
|
language: shell
|
||||||
before_install:
|
before_install:
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
- 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 python2 curl"
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x buster main' >/etc/apt/sources.list.d/nodesource.list"
|
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/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 "apt-get update && apt-get install -y nodejs"
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
script: sudo docker exec sharp sh -c "npm test"
|
||||||
|
|
||||||
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
|
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
|
||||||
@@ -97,43 +35,28 @@ jobs:
|
|||||||
dist: bionic
|
dist: bionic
|
||||||
language: shell
|
language: shell
|
||||||
before_install:
|
before_install:
|
||||||
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
- 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 python2 curl"
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||||
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||||
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x buster main' >/etc/apt/sources.list.d/nodesource.list"
|
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x sid main' >/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 "apt-get update && apt-get install -y nodejs"
|
||||||
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
|
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||||
script: sudo docker exec sharp sh -c "npm test"
|
script: sudo docker exec sharp sh -c "npm test"
|
||||||
|
|
||||||
- name: "macOS (10.13) - Node.js 10"
|
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 15"
|
||||||
os: osx
|
arch: arm64
|
||||||
osx_image: xcode10.1
|
|
||||||
language: node_js
|
|
||||||
node_js: "10"
|
|
||||||
|
|
||||||
- name: "macOS (10.13) - Node.js 12"
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode10.1
|
|
||||||
language: node_js
|
|
||||||
node_js: "12"
|
|
||||||
before_install: unset prebuild_upload
|
|
||||||
|
|
||||||
- name: "macOS (10.13) - Node.js 14"
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode10.1
|
|
||||||
language: node_js
|
|
||||||
node_js: "14"
|
|
||||||
before_install: unset prebuild_upload
|
|
||||||
|
|
||||||
- name: "Unit test coverage report"
|
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
language: node_js
|
language: shell
|
||||||
node_js: "13"
|
before_install:
|
||||||
before_install: unset prebuild_upload
|
- sudo chown 0.0 ${PWD}
|
||||||
after_success:
|
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
|
||||||
- npm install coveralls
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
|
||||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
|
||||||
|
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_15.x sid main' >/etc/apt/sources.list.d/nodesource.list"
|
||||||
|
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
|
||||||
|
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
|
||||||
|
script: sudo docker exec sharp sh -c "npm test"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
npm: false
|
npm: false
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
|
||||||
|
|
||||||
Resizing an image is typically 4x-5x faster than using the
|
Resizing an image is typically 4x-5x faster than using the
|
||||||
quickest ImageMagick and GraphicsMagick settings
|
quickest ImageMagick and GraphicsMagick settings
|
||||||
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
|
|||||||
As well as image resizing, operations such as
|
As well as image resizing, operations such as
|
||||||
rotation, extraction, compositing and gamma correction are available.
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
|
Most modern macOS, Windows and Linux systems running Node.js v10+
|
||||||
do not require any additional install or runtime dependencies.
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
24
appveyor.yml
@@ -1,26 +1,18 @@
|
|||||||
os: Visual Studio 2017
|
os: Visual Studio 2019
|
||||||
version: "{build}"
|
version: "{build}"
|
||||||
build: off
|
build: off
|
||||||
|
platform: x86
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: "10"
|
- nodejs_version: "10"
|
||||||
platform: x86
|
prebuild: true
|
||||||
- nodejs_version: "10"
|
|
||||||
platform: x64
|
|
||||||
- nodejs_version: "12"
|
- nodejs_version: "12"
|
||||||
platform: x86
|
|
||||||
prebuild_upload: ""
|
|
||||||
- nodejs_version: "12"
|
|
||||||
platform: x64
|
|
||||||
prebuild_upload: ""
|
|
||||||
- nodejs_version: "14.2.0"
|
|
||||||
platform: x86
|
|
||||||
prebuild_upload: ""
|
|
||||||
- nodejs_version: "14"
|
- nodejs_version: "14"
|
||||||
platform: x64
|
- nodejs_version: "15"
|
||||||
prebuild_upload: ""
|
|
||||||
install:
|
install:
|
||||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
|
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
|
||||||
- npm install
|
- npm install --build-from-source
|
||||||
test_script:
|
test_script:
|
||||||
- npm test
|
- npm test
|
||||||
|
on_success:
|
||||||
|
- if [%prebuild%] == [true] if [%APPVEYOR_REPO_TAG%] == [true] npx prebuild --runtime napi --target 3
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
'src/sharp.cc'
|
'src/sharp.cc'
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'<!@(node -p "require(\'node-addon-api\').include")',
|
'<!(node -p "require(\'node-addon-api\').include_dir")',
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['use_global_libvips == "true"', {
|
['use_global_libvips == "true"', {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
smaller, web-friendly JPEG, PNG, AVIF and WebP images of varying dimensions.
|
||||||
|
|
||||||
Resizing an image is typically 4x-5x faster than using the
|
Resizing an image is typically 4x-5x faster than using the
|
||||||
quickest ImageMagick and GraphicsMagick settings
|
quickest ImageMagick and GraphicsMagick settings
|
||||||
@@ -16,14 +16,14 @@ Lanczos resampling ensures quality is not sacrificed for speed.
|
|||||||
As well as image resizing, operations such as
|
As well as image resizing, operations such as
|
||||||
rotation, extraction, compositing and gamma correction are available.
|
rotation, extraction, compositing and gamma correction are available.
|
||||||
|
|
||||||
Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
|
Most modern macOS, Windows and Linux systems running Node.js v10+
|
||||||
do not require any additional install or runtime dependencies.
|
do not require any additional install or runtime dependencies.
|
||||||
|
|
||||||
### Formats
|
### Formats
|
||||||
|
|
||||||
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
|
This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG images.
|
||||||
|
|
||||||
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
|
Output images can be in JPEG, PNG, WebP, AVIF and TIFF formats as well as uncompressed raw pixel data.
|
||||||
|
|
||||||
Streams, Buffer objects and the filesystem can be used for input and output.
|
Streams, Buffer objects and the filesystem can be used for input and output.
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Channel ordering follows vips convention:
|
|||||||
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
||||||
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
||||||
|
|
||||||
Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
|
Buffers may be any of the image formats supported by sharp.
|
||||||
For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||||
|
|
||||||
JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
|
JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
|
||||||
When using Stream based output, derived attributes are available from the `info` event.
|
When using Stream based output, derived attributes are available from the `info` event.
|
||||||
|
|
||||||
Non-critical problems encountered during processing are emitted as `warning` events.
|
Non-critical problems encountered during processing are emitted as `warning` events.
|
||||||
@@ -14,9 +14,9 @@ Implements the [stream.Duplex][1] class.
|
|||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `input` **([Buffer][2] \| [string][3])?** if present, can be
|
- `input` **([Buffer][2] \| [string][3])?** if present, can be
|
||||||
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
|
||||||
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
||||||
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||||
- `options` **[Object][4]?** if present, is an Object with optional attributes.
|
- `options` **[Object][4]?** if present, is an Object with optional attributes.
|
||||||
- `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
|
- `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
|
||||||
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
|
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
|
||||||
@@ -25,9 +25,9 @@ Implements the [stream.Duplex][1] class.
|
|||||||
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
|
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
|
||||||
- `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
|
- `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
|
||||||
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
|
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
|
||||||
- `options.density` **[number][6]** number representing the DPI for vector images. (optional, default `72`)
|
- `options.density` **[number][6]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
|
||||||
- `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
- `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
||||||
- `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
|
- `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
|
||||||
- `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
|
- `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
|
||||||
- `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
|
- `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
|
||||||
- `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
- `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||||
|
|||||||
@@ -65,6 +65,61 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
|||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
|
## affine
|
||||||
|
|
||||||
|
Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
|
||||||
|
|
||||||
|
You must provide an array of length 4 or a 2x2 affine transformation matrix.
|
||||||
|
By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
|
||||||
|
A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
|
||||||
|
|
||||||
|
In the case of a 2x2 matrix, the transform is:
|
||||||
|
|
||||||
|
- X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
|
||||||
|
- Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
- x and y are the coordinates in input image.
|
||||||
|
- X and Y are the coordinates in output image.
|
||||||
|
- (0,0) is the upper left corner.
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- `matrix` **([Array][7]<[Array][7]<[number][1]>> | [Array][7]<[number][1]>)** affine transformation matrix
|
||||||
|
- `options` **[Object][2]?** if present, is an Object with optional attributes.
|
||||||
|
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
|
||||||
|
- `options.idx` **[Number][1]** input horizontal offset (optional, default `0`)
|
||||||
|
- `options.idy` **[Number][1]** input vertical offset (optional, default `0`)
|
||||||
|
- `options.odx` **[Number][1]** output horizontal offset (optional, default `0`)
|
||||||
|
- `options.ody` **[Number][1]** output vertical offset (optional, default `0`)
|
||||||
|
- `options.interpolator` **[String][3]** interpolator (optional, default `sharp.interpolators.bicubic`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const pipeline = sharp()
|
||||||
|
.affine([[1, 0.3], [0.1, 0.7]], {
|
||||||
|
background: 'white',
|
||||||
|
interpolate: sharp.interpolators.nohalo
|
||||||
|
})
|
||||||
|
.toBuffer((err, outputBuffer, info) => {
|
||||||
|
// outputBuffer contains the transformed image
|
||||||
|
// info.width and info.height contain the new dimensions
|
||||||
|
});
|
||||||
|
|
||||||
|
inputStream
|
||||||
|
.pipe(pipeline);
|
||||||
|
```
|
||||||
|
|
||||||
|
- Throws **[Error][5]** Invalid parameters
|
||||||
|
|
||||||
|
Returns **Sharp**
|
||||||
|
|
||||||
|
**Meta**
|
||||||
|
|
||||||
|
- **since**: 0.27.0
|
||||||
|
|
||||||
## sharpen
|
## sharpen
|
||||||
|
|
||||||
Sharpen the image.
|
Sharpen the image.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Write output image data to a file.
|
Write output image data to a file.
|
||||||
|
|
||||||
If an explicit output format is not selected, it will be inferred from the extension,
|
If an explicit output format is not selected, it will be inferred from the extension,
|
||||||
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
|
with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
|
||||||
Note that raw pixel data is only supported for buffer output.
|
Note that raw pixel data is only supported for buffer output.
|
||||||
|
|
||||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
@@ -42,7 +42,7 @@ Returns **[Promise][5]<[Object][6]>** when no callback is provided
|
|||||||
## toBuffer
|
## toBuffer
|
||||||
|
|
||||||
Write output to a Buffer.
|
Write output to a Buffer.
|
||||||
JPEG, PNG, WebP, TIFF and RAW output are supported.
|
JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
|
||||||
|
|
||||||
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||||
|
|
||||||
@@ -299,23 +299,43 @@ sharp('input.svg')
|
|||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
## heif
|
## avif
|
||||||
|
|
||||||
Use these HEIF options for output image.
|
Use these AVIF options for output image.
|
||||||
|
|
||||||
Support for HEIF (HEIC/AVIF) is experimental.
|
Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||||
Do not use this in production systems.
|
most web browsers do not display these properly.
|
||||||
|
|
||||||
Requires a custom, globally-installed libvips compiled with support for libheif.
|
|
||||||
|
|
||||||
Most versions of libheif support only the patent-encumbered HEVC compression format.
|
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `options` **[Object][6]?** output options
|
- `options` **[Object][6]?** output options
|
||||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||||
- `options.compression` **[boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
|
|
||||||
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
|
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||||
|
|
||||||
|
|
||||||
|
- Throws **[Error][4]** Invalid options
|
||||||
|
|
||||||
|
Returns **Sharp**
|
||||||
|
|
||||||
|
**Meta**
|
||||||
|
|
||||||
|
- **since**: 0.27.0
|
||||||
|
|
||||||
|
## heif
|
||||||
|
|
||||||
|
Use these HEIF options for output image.
|
||||||
|
|
||||||
|
Support for patent-encumbered HEIC images requires the use of a
|
||||||
|
globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- `options` **[Object][6]?** output options
|
||||||
|
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||||
|
- `options.compression` **[boolean][7]** compression format: av1, hevc (optional, default `'av1'`)
|
||||||
|
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
|
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][4]** Invalid options
|
- Throws **[Error][4]** Invalid options
|
||||||
@@ -372,6 +392,8 @@ Warning: multiple sharp instances concurrently producing tile output can expose
|
|||||||
- `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
- `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
|
||||||
- `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
- `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
|
||||||
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
|
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||||
|
- `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
|
||||||
|
- `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,8 @@ Returns **Sharp**
|
|||||||
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||||
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||||
|
|
||||||
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||||
|
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,36 @@ console.log(sharp.format);
|
|||||||
|
|
||||||
Returns **[Object][1]**
|
Returns **[Object][1]**
|
||||||
|
|
||||||
|
## interpolators
|
||||||
|
|
||||||
|
An Object containing the available interpolators and their proper values
|
||||||
|
|
||||||
|
Type: [string][2]
|
||||||
|
|
||||||
|
### nearest
|
||||||
|
|
||||||
|
[Nearest neighbour interpolation][3]. Suitable for image enlargement only.
|
||||||
|
|
||||||
|
### bilinear
|
||||||
|
|
||||||
|
[Bilinear interpolation][4]. Faster than bicubic but with less smooth results.
|
||||||
|
|
||||||
|
### bicubic
|
||||||
|
|
||||||
|
[Bicubic interpolation][5] (the default).
|
||||||
|
|
||||||
|
### locallyBoundedBicubic
|
||||||
|
|
||||||
|
[LBB interpolation][6]. Prevents some "[acutance][7]" but typically reduces performance by a factor of 2.
|
||||||
|
|
||||||
|
### nohalo
|
||||||
|
|
||||||
|
[Nohalo interpolation][8]. Prevents acutance but typically reduces performance by a factor of 3.
|
||||||
|
|
||||||
|
### vertexSplitQuadraticBasisSpline
|
||||||
|
|
||||||
|
[VSQBS interpolation][9]. Prevents "staircasing" when enlarging.
|
||||||
|
|
||||||
## versions
|
## versions
|
||||||
|
|
||||||
An Object containing the version numbers of libvips and its dependencies.
|
An Object containing the version numbers of libvips and its dependencies.
|
||||||
@@ -31,10 +61,10 @@ useful for determining how much working memory is required for a particular task
|
|||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `options` **([Object][1] \| [boolean][2])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)
|
- `options` **([Object][1] \| [boolean][10])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)
|
||||||
- `options.memory` **[number][3]** is the maximum memory in MB to use for this cache (optional, default `50`)
|
- `options.memory` **[number][11]** is the maximum memory in MB to use for this cache (optional, default `50`)
|
||||||
- `options.files` **[number][3]** is the maximum number of files to hold open (optional, default `20`)
|
- `options.files` **[number][11]** is the maximum number of files to hold open (optional, default `20`)
|
||||||
- `options.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`)
|
- `options.items` **[number][11]** is the maximum number of operations to cache (optional, default `100`)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -64,7 +94,7 @@ This method always returns the current concurrency.
|
|||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `concurrency` **[number][3]?**
|
- `concurrency` **[number][11]?**
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -74,7 +104,7 @@ sharp.concurrency(2); // 2
|
|||||||
sharp.concurrency(0); // 4
|
sharp.concurrency(0); // 4
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns **[number][3]** concurrency
|
Returns **[number][11]** concurrency
|
||||||
|
|
||||||
## queue
|
## queue
|
||||||
|
|
||||||
@@ -116,7 +146,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
|
|||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `simd` **[boolean][2]** (optional, default `true`)
|
- `simd` **[boolean][10]** (optional, default `true`)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -130,10 +160,26 @@ const simd = sharp.simd(false);
|
|||||||
// prevent libvips from using liborc at runtime
|
// prevent libvips from using liborc at runtime
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns **[boolean][2]**
|
Returns **[boolean][10]**
|
||||||
|
|
||||||
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||||
|
|
||||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||||
|
|
||||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
[3]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
|
||||||
|
|
||||||
|
[4]: http://en.wikipedia.org/wiki/Bilinear_interpolation
|
||||||
|
|
||||||
|
[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation
|
||||||
|
|
||||||
|
[6]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100
|
||||||
|
|
||||||
|
[7]: http://en.wikipedia.org/wiki/Acutance
|
||||||
|
|
||||||
|
[8]: http://eprints.soton.ac.uk/268086/
|
||||||
|
|
||||||
|
[9]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
|
||||||
|
|
||||||
|
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||||
|
|
||||||
|
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||||
|
|||||||
@@ -1,10 +1,70 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.27 - *avif*
|
||||||
|
|
||||||
|
Requires libvips v8.10.5
|
||||||
|
|
||||||
|
### 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*
|
## v0.26 - *zoom*
|
||||||
|
|
||||||
Requires libvips v8.10.0
|
Requires libvips v8.10.0
|
||||||
|
|
||||||
### v0.26.0 - TBD
|
### 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+.
|
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.
|
||||||
|
|
||||||
|
|||||||
@@ -197,3 +197,12 @@ GitHub: https://github.com/deftomat
|
|||||||
|
|
||||||
Name: Robert O'Rourke
|
Name: Robert O'Rourke
|
||||||
GitHub: https://github.com/roborourke
|
GitHub: https://github.com/roborourke
|
||||||
|
|
||||||
|
Name: Denis Soldatov
|
||||||
|
GitHub: https://github.com/derom
|
||||||
|
|
||||||
|
Name: Stefan Probst
|
||||||
|
GitHub: https://github.com/stefanprobst
|
||||||
|
|
||||||
|
Name: Thomas Beiganz
|
||||||
|
GitHub: https://github.com/beig
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions">
|
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
||||||
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
||||||
connect-src 'self' https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
connect-src 'self' https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
||||||
script-src 'sha256-iXDHv+t2aGdJcmEvwHfqmGH4SrOSx+P8rCF6+WLLwXA=' 'unsafe-eval'
|
script-src 'unsafe-inline' 'unsafe-eval'
|
||||||
https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js
|
https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js
|
||||||
https://www.google-analytics.com/analytics.js;">
|
https://www.google-analytics.com/analytics.js;">
|
||||||
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
|
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
|
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
|
||||||
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
|
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
|
||||||
<link rel="author" href="/humans.txt" type="text/plain">
|
<link rel="author" href="/humans.txt" type="text/plain">
|
||||||
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.4/docs/README.md" as="fetch" type="text/markdown" crossorigin>
|
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.0/docs/README.md" as="fetch" type="text/markdown" crossorigin>
|
||||||
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
|
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
|
||||||
<link rel="dns-prefetch" href="https://pixel.plumbing">
|
<link rel="dns-prefetch" href="https://pixel.plumbing">
|
||||||
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
docuteApiTitlePlugin,
|
docuteApiTitlePlugin,
|
||||||
docuteApiSearchPlugin
|
docuteApiSearchPlugin
|
||||||
],
|
],
|
||||||
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.4/docs',
|
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.0/docs',
|
||||||
nav: [
|
nav: [
|
||||||
{
|
{
|
||||||
title: 'Funding',
|
title: 'Funding',
|
||||||
|
|||||||
@@ -10,31 +10,34 @@ yarn add sharp
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* Node.js v10.16.0+
|
* Node.js v10+
|
||||||
|
|
||||||
## Prebuilt binaries
|
## Prebuilt binaries
|
||||||
|
|
||||||
Ready-compiled sharp and libvips binaries are provided for use with
|
Ready-compiled sharp and libvips binaries are provided for use with
|
||||||
Node.js v10.16.0+ on the most common platforms:
|
Node.js v10+ on the most common platforms:
|
||||||
|
|
||||||
* macOS x64 (>= 10.13)
|
* macOS x64 (>= 10.13)
|
||||||
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
|
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
|
||||||
* Linux ARM64 (glibc >= 2.29)
|
* Linux ARM64 (glibc >= 2.29)
|
||||||
* Windows
|
* Windows x64
|
||||||
|
* Windows x86
|
||||||
|
|
||||||
A ~7MB tarball containing libvips and its most commonly used dependencies
|
A ~9MB tarball containing libvips and its most commonly used dependencies
|
||||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||||
|
|
||||||
This provides support for the
|
This provides support for the
|
||||||
JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
|
JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
|
||||||
|
|
||||||
The following platforms have prebuilt libvips but not sharp:
|
The following platforms have prebuilt libvips but not sharp:
|
||||||
|
|
||||||
* Linux ARMv6
|
* Linux ARMv6
|
||||||
* Linux ARMv7 (glibc >= 2.28)
|
* Linux ARMv7 (glibc >= 2.28)
|
||||||
|
* Windows ARM64
|
||||||
|
|
||||||
The following platforms require compilation of both libvips and sharp from source:
|
The following platforms require compilation of both libvips and sharp from source:
|
||||||
|
|
||||||
|
* macOS ARM64
|
||||||
* Linux x86
|
* Linux x86
|
||||||
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
|
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
|
||||||
* Linux ARM64 (glibc <= 2.28, musl)
|
* Linux ARM64 (glibc <= 2.28, musl)
|
||||||
@@ -69,7 +72,7 @@ The use of a globally-installed libvips is unsupported on Windows.
|
|||||||
This module will be compiled from source at `npm install` time when:
|
This module will be compiled from source at `npm install` time when:
|
||||||
|
|
||||||
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
|
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
|
||||||
* prebuilt binaries do not exist for the current platform and Node.js version, or
|
* prebuilt sharp binaries do not exist for the current platform, or
|
||||||
* when the `npm install --build-from-source` flag is used.
|
* when the `npm install --build-from-source` flag is used.
|
||||||
|
|
||||||
Building from source requires:
|
Building from source requires:
|
||||||
@@ -89,11 +92,14 @@ To install the prebuilt libvips binaries from a custom URL,
|
|||||||
set the `sharp_libvips_binary_host` npm config option
|
set the `sharp_libvips_binary_host` npm config option
|
||||||
or the `npm_config_sharp_libvips_binary_host` environment variable.
|
or the `npm_config_sharp_libvips_binary_host` environment variable.
|
||||||
|
|
||||||
The version subpath and file name are appended to these.
|
The version subpath and file name are appended to these. There should be tarballs available
|
||||||
|
that are compressed with both gzip and Brotli, as the format downloaded will vary depending
|
||||||
|
on whether the user's version of Node supports Brotli decompression (Node.js v10.16.0+)
|
||||||
|
|
||||||
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
|
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
|
||||||
and the libvips version is `1.2.3` then the resultant URL will be
|
and the libvips version is `1.2.3` then the resultant URL will be
|
||||||
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br`.
|
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br` or
|
||||||
|
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.gz`.
|
||||||
|
|
||||||
See the Chinese mirror below for a further example.
|
See the Chinese mirror below for a further example.
|
||||||
|
|
||||||
@@ -147,10 +153,18 @@ The binaries in the `node_modules` directory of the
|
|||||||
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
|
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
|
||||||
must be for the Linux x64 platform.
|
must be for the Linux x64 platform.
|
||||||
|
|
||||||
On machines other than Linux x64, such as macOS and Windows, run the following:
|
When building your deployment package on machines other than Linux x64 (glibc),
|
||||||
|
run the following commands:
|
||||||
|
|
||||||
|
macOS:
|
||||||
```sh
|
```sh
|
||||||
rm -rf node_modules/sharp
|
rm -rf node_modules/sharp
|
||||||
|
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
```sh
|
||||||
|
rmdir /s /q node_modules/sharp
|
||||||
npm install --arch=x64 --platform=linux sharp
|
npm install --arch=x64 --platform=linux sharp
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -164,20 +178,6 @@ docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
|
|||||||
To get the best performance select the largest memory available.
|
To get the best performance select the largest memory available.
|
||||||
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
|
||||||
|
|
||||||
## Electron
|
|
||||||
|
|
||||||
Electron provides versions of the V8 JavaScript engine
|
|
||||||
that are incompatible with Node.js.
|
|
||||||
To ensure the correct binaries are used, run the following:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
npx electron-rebuild
|
|
||||||
```
|
|
||||||
|
|
||||||
Further help can be found at
|
|
||||||
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)
|
|
||||||
|
|
||||||
## Worker threads
|
## Worker threads
|
||||||
|
|
||||||
The main thread must call `require('sharp')`
|
The main thread must call `require('sharp')`
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ A test to benchmark the performance of this module relative to alternatives.
|
|||||||
|
|
||||||
## The contenders
|
## The contenders
|
||||||
|
|
||||||
* [jimp](https://www.npmjs.com/package/jimp) v0.9.3 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.3.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.5 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||||
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
||||||
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||||
* sharp v0.24.0 / libvips v8.9.0 - Caching within libvips disabled to ensure a fair comparison.
|
* sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
|
||||||
|
|
||||||
## The task
|
## The task
|
||||||
|
|
||||||
@@ -18,25 +18,25 @@ then compress to JPEG at a "quality" setting of 80.
|
|||||||
|
|
||||||
## Test environment
|
## Test environment
|
||||||
|
|
||||||
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
|
* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
|
||||||
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
|
* Ubuntu 20.10 (ami-046cdbcee95cdd75c)
|
||||||
* Node.js v12.14.1
|
* Node.js v14.15.3
|
||||||
|
|
||||||
## Results
|
## Results
|
||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :----------------- | :----- | :----- | ------: | -------: |
|
| :----------------- | :----- | :----- | ------: | -------: |
|
||||||
| jimp | buffer | buffer | 0.72 | 1.0 |
|
| jimp | buffer | buffer | 0.77 | 1.0 |
|
||||||
| mapnik | buffer | buffer | 3.02 | 4.2 |
|
| mapnik | buffer | buffer | 3.39 | 4.4 |
|
||||||
| gm | buffer | buffer | 3.90 | 5.4 |
|
| gm | buffer | buffer | 4.30 | 5.6 |
|
||||||
| gm | file | file | 3.94 | 5.5 |
|
| gm | file | file | 4.33 | 5.6 |
|
||||||
| imagemagick | file | file | 4.30 | 6.0 |
|
| imagemagick | file | file | 4.39 | 5.7 |
|
||||||
| sharp | stream | stream | 23.65 | 32.8 |
|
| sharp | stream | stream | 23.81 | 30.9 |
|
||||||
| sharp | file | file | 24.66 | 34.3 |
|
| sharp | file | file | 25.09 | 32.6 |
|
||||||
| sharp | buffer | buffer | 25.14 | 34.9 |
|
| sharp | buffer | buffer | 25.60 | 33.2 |
|
||||||
|
|
||||||
Greater libvips performance can be expected with caching enabled (default)
|
Greater libvips performance can be expected with caching enabled (default)
|
||||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
and using 4+ core machines, especially those with larger L1/L2 CPU caches.
|
||||||
|
|
||||||
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const { extractDescription, extractKeywords } = require('./extract');
|
const { extractDescription, extractKeywords } = require('./extract');
|
||||||
|
|
||||||
const searchIndex = [];
|
const searchIndex = [];
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
|
const contents = fs.readFileSync(path.join(__dirname, '..', 'install.md'), 'utf8');
|
||||||
const matches = contents.matchAll(
|
const matches = contents.matchAll(
|
||||||
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
|
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
|
||||||
);
|
);
|
||||||
@@ -34,7 +35,7 @@ for (const match of matches) {
|
|||||||
'colour',
|
'colour',
|
||||||
'utility'
|
'utility'
|
||||||
].forEach((section) => {
|
].forEach((section) => {
|
||||||
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
|
const contents = fs.readFileSync(path.join(__dirname, '..', `api-${section}.md`), 'utf8');
|
||||||
const matches = contents.matchAll(
|
const matches = contents.matchAll(
|
||||||
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
|
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
|
||||||
);
|
);
|
||||||
@@ -54,6 +55,6 @@ for (const match of matches) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${__dirname}/../search-index.json`,
|
path.join(__dirname, '..', 'search-index.json'),
|
||||||
JSON.stringify(searchIndex)
|
JSON.stringify(searchIndex)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,65 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const stopWords = [
|
const stopWords = require('./stop-words');
|
||||||
'a',
|
|
||||||
'about',
|
|
||||||
'all',
|
|
||||||
'already',
|
|
||||||
'always',
|
|
||||||
'an',
|
|
||||||
'and',
|
|
||||||
'any',
|
|
||||||
'are',
|
|
||||||
'as',
|
|
||||||
'at',
|
|
||||||
'be',
|
|
||||||
'been',
|
|
||||||
'by',
|
|
||||||
'can',
|
|
||||||
'do',
|
|
||||||
'does',
|
|
||||||
'each',
|
|
||||||
'either',
|
|
||||||
'etc',
|
|
||||||
'for',
|
|
||||||
'from',
|
|
||||||
'get',
|
|
||||||
'gets',
|
|
||||||
'has',
|
|
||||||
'have',
|
|
||||||
'how',
|
|
||||||
'if',
|
|
||||||
'in',
|
|
||||||
'is',
|
|
||||||
'it',
|
|
||||||
'its',
|
|
||||||
'may',
|
|
||||||
'more',
|
|
||||||
'much',
|
|
||||||
'no',
|
|
||||||
'not',
|
|
||||||
'of',
|
|
||||||
'on',
|
|
||||||
'or',
|
|
||||||
'over',
|
|
||||||
'set',
|
|
||||||
'sets',
|
|
||||||
'should',
|
|
||||||
'that',
|
|
||||||
'the',
|
|
||||||
'their',
|
|
||||||
'there',
|
|
||||||
'therefore',
|
|
||||||
'these',
|
|
||||||
'this',
|
|
||||||
'to',
|
|
||||||
'use',
|
|
||||||
'using',
|
|
||||||
'when',
|
|
||||||
'which',
|
|
||||||
'will',
|
|
||||||
'with'
|
|
||||||
];
|
|
||||||
|
|
||||||
const extractDescription = (str) =>
|
const extractDescription = (str) =>
|
||||||
str
|
str
|
||||||
@@ -71,10 +12,13 @@ const extractDescription = (str) =>
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
const extractKeywords = (str) =>
|
const extractKeywords = (str) =>
|
||||||
str
|
[
|
||||||
.split(/[ -/]/)
|
...new Set(
|
||||||
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
|
str
|
||||||
.filter((word) => word.length > 2 && !stopWords.includes(word))
|
.split(/[ -/]/)
|
||||||
.join(' ');
|
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
|
||||||
|
.filter((word) => word.length > 2 && !stopWords.includes(word))
|
||||||
|
)
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
module.exports = { extractDescription, extractKeywords };
|
module.exports = { extractDescription, extractKeywords };
|
||||||
|
|||||||
71
docs/search-index/stop-words.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
'about',
|
||||||
|
'after',
|
||||||
|
'all',
|
||||||
|
'already',
|
||||||
|
'alternative',
|
||||||
|
'always',
|
||||||
|
'and',
|
||||||
|
'any',
|
||||||
|
'are',
|
||||||
|
'been',
|
||||||
|
'before',
|
||||||
|
'can',
|
||||||
|
'containing',
|
||||||
|
'default',
|
||||||
|
'does',
|
||||||
|
'each',
|
||||||
|
'either',
|
||||||
|
'etc',
|
||||||
|
'for',
|
||||||
|
'from',
|
||||||
|
'get',
|
||||||
|
'gets',
|
||||||
|
'given',
|
||||||
|
'has',
|
||||||
|
'have',
|
||||||
|
'how',
|
||||||
|
'image',
|
||||||
|
'its',
|
||||||
|
'may',
|
||||||
|
'more',
|
||||||
|
'most',
|
||||||
|
'much',
|
||||||
|
'must',
|
||||||
|
'non',
|
||||||
|
'not',
|
||||||
|
'occur',
|
||||||
|
'occurs',
|
||||||
|
'over',
|
||||||
|
'perform',
|
||||||
|
'performs',
|
||||||
|
'provide',
|
||||||
|
'provided',
|
||||||
|
'set',
|
||||||
|
'sets',
|
||||||
|
'should',
|
||||||
|
'spelling',
|
||||||
|
'support',
|
||||||
|
'supported',
|
||||||
|
'take',
|
||||||
|
'that',
|
||||||
|
'the',
|
||||||
|
'their',
|
||||||
|
'there',
|
||||||
|
'therefore',
|
||||||
|
'these',
|
||||||
|
'this',
|
||||||
|
'use',
|
||||||
|
'used',
|
||||||
|
'using',
|
||||||
|
'value',
|
||||||
|
'values',
|
||||||
|
'when',
|
||||||
|
'which',
|
||||||
|
'while',
|
||||||
|
'will',
|
||||||
|
'with',
|
||||||
|
'without'
|
||||||
|
];
|
||||||
@@ -25,6 +25,7 @@ const minimumGlibcVersionByArch = {
|
|||||||
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
||||||
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
||||||
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
||||||
|
const supportsBrotli = ('BrotliDecompress' in zlib);
|
||||||
|
|
||||||
const fail = function (err) {
|
const fail = function (err) {
|
||||||
npmLog.error('sharp', err.message);
|
npmLog.error('sharp', err.message);
|
||||||
@@ -43,7 +44,7 @@ const extractTarball = function (tarPath) {
|
|||||||
libvips.mkdirSync(versionedVendorPath);
|
libvips.mkdirSync(versionedVendorPath);
|
||||||
stream.pipeline(
|
stream.pipeline(
|
||||||
fs.createReadStream(tarPath),
|
fs.createReadStream(tarPath),
|
||||||
new zlib.BrotliDecompress(),
|
supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
|
||||||
tarFs.extract(versionedVendorPath),
|
tarFs.extract(versionedVendorPath),
|
||||||
function (err) {
|
function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -58,6 +59,7 @@ const extractTarball = function (tarPath) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const useGlobalLibvips = libvips.useGlobalLibvips();
|
const useGlobalLibvips = libvips.useGlobalLibvips();
|
||||||
|
|
||||||
if (useGlobalLibvips) {
|
if (useGlobalLibvips) {
|
||||||
const globalLibvipsVersion = libvips.globalLibvipsVersion();
|
const globalLibvipsVersion = libvips.globalLibvipsVersion();
|
||||||
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
|
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
|
||||||
@@ -80,8 +82,16 @@ try {
|
|||||||
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
|
||||||
|
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
|
||||||
|
throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = supportsBrotli ? 'br' : 'gz';
|
||||||
|
|
||||||
// Download to per-process temporary file
|
// Download to per-process temporary file
|
||||||
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
|
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
|
||||||
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
||||||
if (fs.existsSync(tarPathCache)) {
|
if (fs.existsSync(tarPathCache)) {
|
||||||
npmLog.info('sharp', `Using cached ${tarPathCache}`);
|
npmLog.info('sharp', `Using cached ${tarPathCache}`);
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { spawnSync } = require('child_process');
|
|
||||||
|
|
||||||
const { prebuild_upload: hasToken, APPVEYOR_REPO_TAG_NAME, TRAVIS_TAG } = process.env;
|
|
||||||
|
|
||||||
if (hasToken && (APPVEYOR_REPO_TAG_NAME || TRAVIS_TAG)) {
|
|
||||||
spawnSync('node',
|
|
||||||
['./node_modules/prebuild/bin.js', '--runtime', 'napi', '--target', '3'],
|
|
||||||
{ shell: true, stdio: 'inherit' }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -85,7 +85,7 @@ function extractChannel (channel) {
|
|||||||
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
||||||
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
||||||
*
|
*
|
||||||
* Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
|
* Buffers may be any of the image formats supported by sharp.
|
||||||
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
||||||
*
|
*
|
||||||
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
|
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
|
||||||
|
|||||||
@@ -105,8 +105,9 @@ function composite (images) {
|
|||||||
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
||||||
blend: 'over',
|
blend: 'over',
|
||||||
tile: false,
|
tile: false,
|
||||||
left: -1,
|
left: 0,
|
||||||
top: -1,
|
top: 0,
|
||||||
|
hasOffset: false,
|
||||||
gravity: 0,
|
gravity: 0,
|
||||||
premultiplied: false
|
premultiplied: false
|
||||||
};
|
};
|
||||||
@@ -125,21 +126,23 @@ function composite (images) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(image.left)) {
|
if (is.defined(image.left)) {
|
||||||
if (is.integer(image.left) && image.left >= 0) {
|
if (is.integer(image.left)) {
|
||||||
composite.left = image.left;
|
composite.left = image.left;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('left', 'positive integer', image.left);
|
throw is.invalidParameterError('left', 'integer', image.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(image.top)) {
|
if (is.defined(image.top)) {
|
||||||
if (is.integer(image.top) && image.top >= 0) {
|
if (is.integer(image.top)) {
|
||||||
composite.top = image.top;
|
composite.top = image.top;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('top', 'positive integer', image.top);
|
throw is.invalidParameterError('top', 'integer', image.top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
|
if (is.defined(image.top) !== is.defined(image.left)) {
|
||||||
throw new Error('Expected both left and top to be set');
|
throw new Error('Expected both left and top to be set');
|
||||||
|
} else {
|
||||||
|
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
|
||||||
}
|
}
|
||||||
if (is.defined(image.gravity)) {
|
if (is.defined(image.gravity)) {
|
||||||
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const debuglog = util.debuglog('sharp');
|
|||||||
/**
|
/**
|
||||||
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||||
*
|
*
|
||||||
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
|
* JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
|
||||||
* When using Stream based output, derived attributes are available from the `info` event.
|
* When using Stream based output, derived attributes are available from the `info` event.
|
||||||
*
|
*
|
||||||
* Non-critical problems encountered during processing are emitted as `warning` events.
|
* Non-critical problems encountered during processing are emitted as `warning` events.
|
||||||
@@ -91,9 +91,9 @@ const debuglog = util.debuglog('sharp');
|
|||||||
* await sharp('in.gif', { animated: true }).toFile('out.webp');
|
* await sharp('in.gif', { animated: true }).toFile('out.webp');
|
||||||
*
|
*
|
||||||
* @param {(Buffer|string)} [input] - if present, can be
|
* @param {(Buffer|string)} [input] - if present, can be
|
||||||
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
* a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
|
||||||
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
||||||
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
||||||
* @param {Object} [options] - if present, is an Object with optional attributes.
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||||
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
||||||
@@ -102,9 +102,9 @@ const debuglog = util.debuglog('sharp');
|
|||||||
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
||||||
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
||||||
* This can reduce memory usage and might improve performance on some systems.
|
* This can reduce memory usage and might improve performance on some systems.
|
||||||
* @param {number} [options.density=72] - number representing the DPI for vector images.
|
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
||||||
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
|
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
|
||||||
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
|
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
|
||||||
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
|
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
|
||||||
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
|
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
|
||||||
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||||
@@ -155,6 +155,13 @@ const Sharp = function (input, options) {
|
|||||||
extendRight: 0,
|
extendRight: 0,
|
||||||
extendBackground: [0, 0, 0, 255],
|
extendBackground: [0, 0, 0, 255],
|
||||||
withoutEnlargement: false,
|
withoutEnlargement: false,
|
||||||
|
affineMatrix: [],
|
||||||
|
affineBackground: [0, 0, 0, 255],
|
||||||
|
affineIdx: 0,
|
||||||
|
affineIdy: 0,
|
||||||
|
affineOdx: 0,
|
||||||
|
affineOdy: 0,
|
||||||
|
affineInterpolator: this.constructor.interpolators.bilinear,
|
||||||
kernel: 'lanczos3',
|
kernel: 'lanczos3',
|
||||||
fastShrinkOnLoad: true,
|
fastShrinkOnLoad: true,
|
||||||
// operations
|
// operations
|
||||||
@@ -226,9 +233,10 @@ const Sharp = function (input, options) {
|
|||||||
tiffTileWidth: 256,
|
tiffTileWidth: 256,
|
||||||
tiffXres: 1.0,
|
tiffXres: 1.0,
|
||||||
tiffYres: 1.0,
|
tiffYres: 1.0,
|
||||||
heifQuality: 80,
|
heifQuality: 50,
|
||||||
heifLossless: false,
|
heifLossless: false,
|
||||||
heifCompression: 'hevc',
|
heifCompression: 'av1',
|
||||||
|
heifSpeed: 5,
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
tileOverlap: 0,
|
tileOverlap: 0,
|
||||||
tileContainer: 'fs',
|
tileContainer: 'fs',
|
||||||
@@ -238,6 +246,7 @@ const Sharp = function (input, options) {
|
|||||||
tileAngle: 0,
|
tileAngle: 0,
|
||||||
tileSkipBlanks: -1,
|
tileSkipBlanks: -1,
|
||||||
tileBackground: [255, 255, 255, 255],
|
tileBackground: [255, 255, 255, 255],
|
||||||
|
tileCentre: false,
|
||||||
linearA: 1,
|
linearA: 1,
|
||||||
linearB: 0,
|
linearB: 0,
|
||||||
// Function to notify of libvips warnings
|
// Function to notify of libvips warnings
|
||||||
|
|||||||
10
lib/input.js
@@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _inputOptionsFromObject (obj) {
|
function _inputOptionsFromObject (obj) {
|
||||||
const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
|
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
|
||||||
return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
|
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
|
||||||
? { raw, density, limitInputPixels, sequentialRead, failOnError }
|
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +57,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
// Density
|
// Density
|
||||||
if (is.defined(inputOptions.density)) {
|
if (is.defined(inputOptions.density)) {
|
||||||
if (is.inRange(inputOptions.density, 1, 2400)) {
|
if (is.inRange(inputOptions.density, 1, 100000)) {
|
||||||
inputDescriptor.density = inputOptions.density;
|
inputDescriptor.density = inputOptions.density;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
|
throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// limitInputPixels
|
// limitInputPixels
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { flatten: flattenArray } = require('array-flatten');
|
||||||
const color = require('color');
|
const color = require('color');
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
@@ -82,6 +83,103 @@ function flop (flop) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
|
||||||
|
*
|
||||||
|
* You must provide an array of length 4 or a 2x2 affine transformation matrix.
|
||||||
|
* By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
|
||||||
|
* A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
|
||||||
|
*
|
||||||
|
* In the case of a 2x2 matrix, the transform is:
|
||||||
|
* - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
|
||||||
|
* - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
|
||||||
|
*
|
||||||
|
* where:
|
||||||
|
* - x and y are the coordinates in input image.
|
||||||
|
* - X and Y are the coordinates in output image.
|
||||||
|
* - (0,0) is the upper left corner.
|
||||||
|
*
|
||||||
|
* @since 0.27.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const pipeline = sharp()
|
||||||
|
* .affine([[1, 0.3], [0.1, 0.7]], {
|
||||||
|
* background: 'white',
|
||||||
|
* interpolate: sharp.interpolators.nohalo
|
||||||
|
* })
|
||||||
|
* .toBuffer((err, outputBuffer, info) => {
|
||||||
|
* // outputBuffer contains the transformed image
|
||||||
|
* // info.width and info.height contain the new dimensions
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* inputStream
|
||||||
|
* .pipe(pipeline);
|
||||||
|
*
|
||||||
|
* @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
|
||||||
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||||
|
* @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
* @param {Number} [options.idx=0] - input horizontal offset
|
||||||
|
* @param {Number} [options.idy=0] - input vertical offset
|
||||||
|
* @param {Number} [options.odx=0] - output horizontal offset
|
||||||
|
* @param {Number} [options.ody=0] - output vertical offset
|
||||||
|
* @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid parameters
|
||||||
|
*/
|
||||||
|
function affine (matrix, options) {
|
||||||
|
const flatMatrix = flattenArray(matrix);
|
||||||
|
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
|
||||||
|
this.options.affineMatrix = flatMatrix;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.defined(options)) {
|
||||||
|
if (is.object(options)) {
|
||||||
|
this._setBackgroundColourOption('affineBackground', options.background);
|
||||||
|
if (is.defined(options.idx)) {
|
||||||
|
if (is.number(options.idx)) {
|
||||||
|
this.options.affineIdx = options.idx;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.idx', 'number', options.idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.idy)) {
|
||||||
|
if (is.number(options.idy)) {
|
||||||
|
this.options.affineIdy = options.idy;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.idy', 'number', options.idy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.odx)) {
|
||||||
|
if (is.number(options.odx)) {
|
||||||
|
this.options.affineOdx = options.odx;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.odx', 'number', options.odx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.ody)) {
|
||||||
|
if (is.number(options.ody)) {
|
||||||
|
this.options.affineOdy = options.ody;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.ody', 'number', options.ody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.interpolator)) {
|
||||||
|
if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
|
||||||
|
this.options.affineInterpolator = options.interpolator;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('options', 'object', options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sharpen the image.
|
* Sharpen the image.
|
||||||
* When used without parameters, performs a fast, mild sharpen of the output image.
|
* When used without parameters, performs a fast, mild sharpen of the output image.
|
||||||
@@ -482,6 +580,7 @@ module.exports = function (Sharp) {
|
|||||||
rotate,
|
rotate,
|
||||||
flip,
|
flip,
|
||||||
flop,
|
flop,
|
||||||
|
affine,
|
||||||
sharpen,
|
sharpen,
|
||||||
median,
|
median,
|
||||||
blur,
|
blur,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const sharp = require('../build/Release/sharp.node');
|
|||||||
const formats = new Map([
|
const formats = new Map([
|
||||||
['heic', 'heif'],
|
['heic', 'heif'],
|
||||||
['heif', 'heif'],
|
['heif', 'heif'],
|
||||||
|
['avif', 'avif'],
|
||||||
['jpeg', 'jpeg'],
|
['jpeg', 'jpeg'],
|
||||||
['jpg', 'jpeg'],
|
['jpg', 'jpeg'],
|
||||||
['png', 'png'],
|
['png', 'png'],
|
||||||
@@ -19,7 +20,7 @@ const formats = new Map([
|
|||||||
* Write output image data to a file.
|
* Write output image data to a file.
|
||||||
*
|
*
|
||||||
* If an explicit output format is not selected, it will be inferred from the extension,
|
* If an explicit output format is not selected, it will be inferred from the extension,
|
||||||
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
|
* with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
|
||||||
* Note that raw pixel data is only supported for buffer output.
|
* Note that raw pixel data is only supported for buffer output.
|
||||||
*
|
*
|
||||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
@@ -71,7 +72,7 @@ function toFile (fileOut, callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Write output to a Buffer.
|
* Write output to a Buffer.
|
||||||
* JPEG, PNG, WebP, TIFF and RAW output are supported.
|
* JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
|
||||||
*
|
*
|
||||||
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
||||||
*
|
*
|
||||||
@@ -556,29 +557,42 @@ function tiff (options) {
|
|||||||
return this._updateFormatOut('tiff', options);
|
return this._updateFormatOut('tiff', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use these AVIF options for output image.
|
||||||
|
*
|
||||||
|
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||||
|
* most web browsers do not display these properly.
|
||||||
|
*
|
||||||
|
* @since 0.27.0
|
||||||
|
*
|
||||||
|
* @param {Object} [options] - output options
|
||||||
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||||
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
|
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid options
|
||||||
|
*/
|
||||||
|
function avif (options) {
|
||||||
|
return this.heif({ ...options, compression: 'av1' });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use these HEIF options for output image.
|
* Use these HEIF options for output image.
|
||||||
*
|
*
|
||||||
* Support for HEIF (HEIC/AVIF) is experimental.
|
* Support for patent-encumbered HEIC images requires the use of a
|
||||||
* Do not use this in production systems.
|
* globally-installed libvips compiled with support for libheif, libde265 and x265.
|
||||||
*
|
|
||||||
* Requires a custom, globally-installed libvips compiled with support for libheif.
|
|
||||||
*
|
|
||||||
* Most versions of libheif support only the patent-encumbered HEVC compression format.
|
|
||||||
*
|
*
|
||||||
* @since 0.23.0
|
* @since 0.23.0
|
||||||
*
|
*
|
||||||
* @param {Object} [options] - output options
|
* @param {Object} [options] - output options
|
||||||
* @param {number} [options.quality=80] - quality, integer 1-100
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||||
* @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
|
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
|
||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
|
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
function heif (options) {
|
function heif (options) {
|
||||||
if (!this.constructor.format.heif.output.buffer) {
|
|
||||||
throw new Error('The heif operation requires libvips to have been installed with support for libheif');
|
|
||||||
}
|
|
||||||
if (is.object(options)) {
|
if (is.object(options)) {
|
||||||
if (is.defined(options.quality)) {
|
if (is.defined(options.quality)) {
|
||||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||||
@@ -595,10 +609,17 @@ function heif (options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(options.compression)) {
|
if (is.defined(options.compression)) {
|
||||||
if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
|
if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
|
||||||
this.options.heifCompression = options.compression;
|
this.options.heifCompression = options.compression;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
|
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.speed)) {
|
||||||
|
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
|
||||||
|
this.options.heifSpeed = options.speed;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,6 +679,8 @@ function raw () {
|
|||||||
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
||||||
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
||||||
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
|
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
|
||||||
|
* @param {boolean} [options.centre=false] centre image in tile.
|
||||||
|
* @param {boolean} [options.center=false] alternative spelling of centre.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
@@ -726,6 +749,11 @@ function tile (options) {
|
|||||||
} else if (is.defined(options.layout) && options.layout === 'google') {
|
} else if (is.defined(options.layout) && options.layout === 'google') {
|
||||||
this.options.tileSkipBlanks = 5;
|
this.options.tileSkipBlanks = 5;
|
||||||
}
|
}
|
||||||
|
// Center image in tile
|
||||||
|
const centre = is.bool(options.center) ? options.center : options.centre;
|
||||||
|
if (is.defined(centre)) {
|
||||||
|
this._setBooleanOption('tileCentre', centre);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Format
|
// Format
|
||||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||||
@@ -884,6 +912,7 @@ module.exports = function (Sharp) {
|
|||||||
png,
|
png,
|
||||||
webp,
|
webp,
|
||||||
tiff,
|
tiff,
|
||||||
|
avif,
|
||||||
heif,
|
heif,
|
||||||
gif,
|
gif,
|
||||||
raw,
|
raw,
|
||||||
|
|||||||
@@ -386,7 +386,8 @@ function extract (options) {
|
|||||||
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||||
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
||||||
*
|
*
|
||||||
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||||
|
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||||
*
|
*
|
||||||
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
|
|||||||
@@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node');
|
|||||||
*/
|
*/
|
||||||
const format = sharp.format();
|
const format = sharp.format();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Object containing the available interpolators and their proper values
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
const interpolators = {
|
||||||
|
/** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
|
||||||
|
nearest: 'nearest',
|
||||||
|
/** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
|
||||||
|
bilinear: 'bilinear',
|
||||||
|
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
|
||||||
|
bicubic: 'bicubic',
|
||||||
|
/** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
|
||||||
|
locallyBoundedBicubic: 'lbb',
|
||||||
|
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
|
||||||
|
nohalo: 'nohalo',
|
||||||
|
/** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
||||||
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Object containing the version numbers of libvips and its dependencies.
|
* An Object containing the version numbers of libvips and its dependencies.
|
||||||
* @member
|
* @member
|
||||||
@@ -146,6 +166,7 @@ module.exports = function (Sharp) {
|
|||||||
Sharp[f.name] = f;
|
Sharp[f.name] = f;
|
||||||
});
|
});
|
||||||
Sharp.format = format;
|
Sharp.format = format;
|
||||||
|
Sharp.interpolators = interpolators;
|
||||||
Sharp.versions = versions;
|
Sharp.versions = versions;
|
||||||
Sharp.queue = queue;
|
Sharp.queue = queue;
|
||||||
};
|
};
|
||||||
|
|||||||
36
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
|
||||||
"version": "0.26.0-beta1",
|
"version": "0.27.0",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://github.com/lovell/sharp",
|
"homepage": "https://github.com/lovell/sharp",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -69,12 +69,15 @@
|
|||||||
"Edward Silverton <e.silverton@gmail.com>",
|
"Edward Silverton <e.silverton@gmail.com>",
|
||||||
"Roman Malieiev <aromaleev@gmail.com>",
|
"Roman Malieiev <aromaleev@gmail.com>",
|
||||||
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
||||||
"Robert O'Rourke <robert@o-rourke.org>"
|
"Robert O'Rourke <robert@o-rourke.org>",
|
||||||
|
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
||||||
|
"Christian Flintrup <chr@gigahost.dk>",
|
||||||
|
"Manan Jadhav <manan@motionden.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||||
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||||
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
|
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
|
||||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||||
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||||
"test-coverage": "./test/coverage/report.sh",
|
"test-coverage": "./test/coverage/report.sh",
|
||||||
@@ -87,7 +90,6 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"binding.gyp",
|
"binding.gyp",
|
||||||
"install/**",
|
"install/**",
|
||||||
"!install/prebuild-ci.js",
|
|
||||||
"lib/**",
|
"lib/**",
|
||||||
"src/**"
|
"src/**"
|
||||||
],
|
],
|
||||||
@@ -99,6 +101,7 @@
|
|||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
"webp",
|
"webp",
|
||||||
|
"avif",
|
||||||
"tiff",
|
"tiff",
|
||||||
"gif",
|
"gif",
|
||||||
"svg",
|
"svg",
|
||||||
@@ -112,39 +115,40 @@
|
|||||||
"vips"
|
"vips"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^3.1.2",
|
"array-flatten": "^3.0.0",
|
||||||
|
"color": "^3.1.3",
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"node-addon-api": "^3.0.0",
|
"node-addon-api": "^3.1.0",
|
||||||
"npmlog": "^4.1.2",
|
"npmlog": "^4.1.2",
|
||||||
"prebuild-install": "^5.3.5",
|
"prebuild-install": "^6.0.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.4",
|
||||||
"simple-get": "^4.0.0",
|
"simple-get": "^4.0.0",
|
||||||
"tar-fs": "^2.1.0",
|
"tar-fs": "^2.1.1",
|
||||||
"tunnel-agent": "^0.6.0"
|
"tunnel-agent": "^0.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^3.2.0",
|
"async": "^3.2.0",
|
||||||
"cc": "^2.0.1",
|
"cc": "^3.0.1",
|
||||||
"decompress-zip": "^0.3.2",
|
"decompress-zip": "^0.3.2",
|
||||||
"documentation": "^13.0.2",
|
"documentation": "^13.1.0",
|
||||||
"exif-reader": "^1.0.3",
|
"exif-reader": "^1.0.3",
|
||||||
"icc": "^2.0.0",
|
"icc": "^2.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^8.1.1",
|
"mocha": "^8.2.1",
|
||||||
"mock-fs": "^4.13.0",
|
"mock-fs": "^4.13.0",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"prebuild": "^10.0.1",
|
"prebuild": "^10.0.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semistandard": "^14.2.3"
|
"semistandard": "^16.0.0"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"config": {
|
"config": {
|
||||||
"libvips": "8.10.0",
|
"libvips": "8.10.5",
|
||||||
"runtime": "napi",
|
"runtime": "napi",
|
||||||
"target": 3
|
"target": 3
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.16.0"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ namespace sharp {
|
|||||||
bool AttrAsBool(Napi::Object obj, std::string attr) {
|
bool AttrAsBool(Napi::Object obj, std::string attr) {
|
||||||
return obj.Get(attr).As<Napi::Boolean>().Value();
|
return obj.Get(attr).As<Napi::Boolean>().Value();
|
||||||
}
|
}
|
||||||
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
|
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
|
||||||
Napi::Array background = obj.Get(attr).As<Napi::Array>();
|
Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
|
||||||
std::vector<double> rgba(background.Length());
|
std::vector<double> vectorOfDouble(napiArray.Length());
|
||||||
for (unsigned int i = 0; i < background.Length(); i++) {
|
for (unsigned int i = 0; i < napiArray.Length(); i++) {
|
||||||
rgba[i] = AttrAsDouble(background, i);
|
vectorOfDouble[i] = AttrAsDouble(napiArray, i);
|
||||||
}
|
}
|
||||||
return rgba;
|
return vectorOfDouble;
|
||||||
}
|
}
|
||||||
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
|
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
|
||||||
Napi::Array array = obj.Get(attr).As<Napi::Array>();
|
Napi::Array array = obj.Get(attr).As<Napi::Array>();
|
||||||
@@ -109,7 +109,7 @@ namespace sharp {
|
|||||||
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
||||||
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
||||||
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
||||||
descriptor->createBackground = AttrAsRgba(input, "createBackground");
|
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
||||||
}
|
}
|
||||||
// Limit input images to a given number of pixels, where pixels = width * height
|
// Limit input images to a given number of pixels, where pixels = width * height
|
||||||
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
||||||
@@ -180,6 +180,7 @@ namespace sharp {
|
|||||||
case ImageType::OPENSLIDE: id = "openslide"; break;
|
case ImageType::OPENSLIDE: id = "openslide"; break;
|
||||||
case ImageType::PPM: id = "ppm"; break;
|
case ImageType::PPM: id = "ppm"; break;
|
||||||
case ImageType::FITS: id = "fits"; break;
|
case ImageType::FITS: id = "fits"; break;
|
||||||
|
case ImageType::EXR: id = "exr"; break;
|
||||||
case ImageType::VIPS: id = "vips"; break;
|
case ImageType::VIPS: id = "vips"; break;
|
||||||
case ImageType::RAW: id = "raw"; break;
|
case ImageType::RAW: id = "raw"; break;
|
||||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||||
@@ -210,6 +211,7 @@ namespace sharp {
|
|||||||
{ "openslideload", ImageType::OPENSLIDE },
|
{ "openslideload", ImageType::OPENSLIDE },
|
||||||
{ "ppmload", ImageType::PPM },
|
{ "ppmload", ImageType::PPM },
|
||||||
{ "fitsload", ImageType::FITS },
|
{ "fitsload", ImageType::FITS },
|
||||||
|
{ "openexrload", ImageType::EXR },
|
||||||
{ "vipsload", ImageType::VIPS },
|
{ "vipsload", ImageType::VIPS },
|
||||||
{ "rawload", ImageType::RAW }
|
{ "rawload", ImageType::RAW }
|
||||||
};
|
};
|
||||||
@@ -485,8 +487,8 @@ namespace sharp {
|
|||||||
Check the proposed format supports the current dimensions.
|
Check the proposed format supports the current dimensions.
|
||||||
*/
|
*/
|
||||||
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
|
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
|
||||||
const int height = image.get_typeof("pageHeight") == G_TYPE_INT
|
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
|
||||||
? image.get_int("pageHeight")
|
? image.get_int(VIPS_META_PAGE_HEIGHT)
|
||||||
: image.height();
|
: image.height();
|
||||||
if (imageType == ImageType::JPEG) {
|
if (imageType == ImageType::JPEG) {
|
||||||
if (image.width() > 65535 || height > 65535) {
|
if (image.width() > 65535 || height > 65535) {
|
||||||
@@ -656,26 +658,18 @@ namespace sharp {
|
|||||||
int top = 0;
|
int top = 0;
|
||||||
|
|
||||||
// assign only if valid
|
// assign only if valid
|
||||||
if (x >= 0 && x < (inWidth - outWidth)) {
|
if (x < (inWidth - outWidth)) {
|
||||||
left = x;
|
left = x;
|
||||||
} else if (x >= (inWidth - outWidth)) {
|
} else if (x >= (inWidth - outWidth)) {
|
||||||
left = inWidth - outWidth;
|
left = inWidth - outWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y >= 0 && y < (inHeight - outHeight)) {
|
if (y < (inHeight - outHeight)) {
|
||||||
top = y;
|
top = y;
|
||||||
} else if (y >= (inHeight - outHeight)) {
|
} else if (y >= (inHeight - outHeight)) {
|
||||||
top = inHeight - outHeight;
|
top = inHeight - outHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the resulting left and top could have been outside the image after calculation from bottom/right edges
|
|
||||||
if (left < 0) {
|
|
||||||
left = 0;
|
|
||||||
}
|
|
||||||
if (top < 0) {
|
|
||||||
top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_tuple(left, top);
|
return std::make_tuple(left, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,10 @@
|
|||||||
|
|
||||||
// Verify platform and compiler compatibility
|
// Verify platform and compiler compatibility
|
||||||
|
|
||||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9))
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
||||||
#error "libvips version 8.9.2+ is required - please see https://sharp.pixelplumbing.com/install"
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
|
||||||
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
|
||||||
|
#error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||||
@@ -92,7 +94,7 @@ namespace sharp {
|
|||||||
double AttrAsDouble(Napi::Object obj, std::string attr);
|
double AttrAsDouble(Napi::Object obj, std::string attr);
|
||||||
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
|
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
|
||||||
bool AttrAsBool(Napi::Object obj, std::string attr);
|
bool AttrAsBool(Napi::Object obj, std::string attr);
|
||||||
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
|
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
|
||||||
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
|
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
|
||||||
|
|
||||||
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
||||||
@@ -111,6 +113,7 @@ namespace sharp {
|
|||||||
OPENSLIDE,
|
OPENSLIDE,
|
||||||
PPM,
|
PPM,
|
||||||
FITS,
|
FITS,
|
||||||
|
EXR,
|
||||||
VIPS,
|
VIPS,
|
||||||
RAW,
|
RAW,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
|||||||
@@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Affine transform
|
||||||
|
if (baton->affineMatrix.size() > 0) {
|
||||||
|
std::vector<double> background;
|
||||||
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
||||||
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
||||||
|
->set("idx", baton->affineIdx)
|
||||||
|
->set("idy", baton->affineIdy)
|
||||||
|
->set("odx", baton->affineOdx)
|
||||||
|
->set("ody", baton->affineOdy)
|
||||||
|
->set("interpolate", baton->affineInterpolator));
|
||||||
|
}
|
||||||
|
|
||||||
// Extend edges
|
// Extend edges
|
||||||
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
@@ -558,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
compositeImage = compositeImage.replicate(across, down);
|
compositeImage = compositeImage.replicate(across, down);
|
||||||
if (composite->left >= 0 && composite->top >= 0) {
|
if (composite->hasOffset) {
|
||||||
std::tie(left, top) = sharp::CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
||||||
composite->left, composite->top);
|
composite->left, composite->top);
|
||||||
@@ -580,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Calculate position
|
// Calculate position
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
if (composite->left >= 0 && composite->top >= 0) {
|
if (composite->hasOffset) {
|
||||||
// Composite image at given offsets
|
// Composite image at given offsets
|
||||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||||
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
||||||
@@ -782,6 +794,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
|
VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
|
->set("optimize_gif_frames", TRUE)
|
||||||
|
->set("optimize_gif_transparency", TRUE)
|
||||||
->set("format", "gif")));
|
->set("format", "gif")));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
@@ -823,6 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
|
->set("speed", baton->heifSpeed)
|
||||||
->set("lossless", baton->heifLossless)));
|
->set("lossless", baton->heifLossless)));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
@@ -871,7 +886,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
bool const isV = sharp::IsV(baton->fileOut);
|
bool const isV = sharp::IsV(baton->fileOut);
|
||||||
bool const mightMatchInput = baton->formatOut == "input";
|
bool const mightMatchInput = baton->formatOut == "input";
|
||||||
bool const willMatchInput = mightMatchInput &&
|
bool const willMatchInput = mightMatchInput &&
|
||||||
!(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
|
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
||||||
|
|
||||||
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
||||||
@@ -925,6 +940,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
|
->set("optimize_gif_frames", TRUE)
|
||||||
|
->set("optimize_gif_transparency", TRUE)
|
||||||
->set("format", "gif"));
|
->set("format", "gif"));
|
||||||
baton->formatOut = "gif";
|
baton->formatOut = "gif";
|
||||||
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
||||||
@@ -950,13 +967,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
||||||
// Write HEIF to file
|
// Write HEIF to file
|
||||||
if (sharp::IsAvif(baton->fileOut)) {
|
|
||||||
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
|
|
||||||
}
|
|
||||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
|
->set("speed", baton->heifSpeed)
|
||||||
->set("lossless", baton->heifLossless));
|
->set("lossless", baton->heifLossless));
|
||||||
baton->formatOut = "heif";
|
baton->formatOut = "heif";
|
||||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||||
@@ -1010,6 +1025,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("suffix", const_cast<char*>(suffix.data()))
|
->set("suffix", const_cast<char*>(suffix.data()))
|
||||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||||
->set("background", baton->tileBackground)
|
->set("background", baton->tileBackground)
|
||||||
|
->set("centre", baton->tileCentre)
|
||||||
->set("skip_blanks", baton->tileSkipBlanks);
|
->set("skip_blanks", baton->tileSkipBlanks);
|
||||||
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
||||||
// not passing anything - libvips will handle choice
|
// not passing anything - libvips will handle choice
|
||||||
@@ -1237,6 +1253,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
||||||
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
||||||
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
||||||
|
composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
|
||||||
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
|
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
|
||||||
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
|
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
|
||||||
baton->composite.push_back(composite);
|
baton->composite.push_back(composite);
|
||||||
@@ -1244,7 +1261,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
// Resize options
|
// Resize options
|
||||||
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
||||||
baton->position = sharp::AttrAsInt32(options, "position");
|
baton->position = sharp::AttrAsInt32(options, "position");
|
||||||
baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
|
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
||||||
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
||||||
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
|
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
|
||||||
// Join Channel Options
|
// Join Channel Options
|
||||||
@@ -1257,7 +1274,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
}
|
}
|
||||||
// Operators
|
// Operators
|
||||||
baton->flatten = sharp::AttrAsBool(options, "flatten");
|
baton->flatten = sharp::AttrAsBool(options, "flatten");
|
||||||
baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
|
baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
|
||||||
baton->negate = sharp::AttrAsBool(options, "negate");
|
baton->negate = sharp::AttrAsBool(options, "negate");
|
||||||
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
||||||
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
||||||
@@ -1279,7 +1296,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
||||||
baton->angle = sharp::AttrAsInt32(options, "angle");
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
||||||
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
||||||
baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
|
baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
|
||||||
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
|
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
|
||||||
baton->flip = sharp::AttrAsBool(options, "flip");
|
baton->flip = sharp::AttrAsBool(options, "flip");
|
||||||
baton->flop = sharp::AttrAsBool(options, "flop");
|
baton->flop = sharp::AttrAsBool(options, "flop");
|
||||||
@@ -1287,8 +1304,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
|
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
|
||||||
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
|
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
|
||||||
baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
|
baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
|
||||||
baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
|
baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
|
||||||
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
|
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
|
||||||
|
baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
|
||||||
|
baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
|
||||||
|
baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
|
||||||
|
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
||||||
|
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
||||||
|
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
||||||
|
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
|
||||||
|
|
||||||
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
||||||
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
|
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
|
||||||
@@ -1371,6 +1395,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
||||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||||
sharp::AttrAsStr(options, "heifCompression").data()));
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
||||||
|
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
||||||
|
|
||||||
// Animated output
|
// Animated output
|
||||||
if (sharp::HasAttr(options, "pageHeight")) {
|
if (sharp::HasAttr(options, "pageHeight")) {
|
||||||
@@ -1387,7 +1412,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
||||||
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
||||||
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
||||||
baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
|
baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
|
||||||
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
|
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
|
||||||
baton->tileContainer = static_cast<VipsForeignDzContainer>(
|
baton->tileContainer = static_cast<VipsForeignDzContainer>(
|
||||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
|
||||||
@@ -1399,6 +1424,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->tileDepth = static_cast<VipsForeignDzDepth>(
|
baton->tileDepth = static_cast<VipsForeignDzDepth>(
|
||||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
||||||
sharp::AttrAsStr(options, "tileDepth").data()));
|
sharp::AttrAsStr(options, "tileDepth").data()));
|
||||||
|
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
||||||
|
|
||||||
// Force random access for certain operations
|
// Force random access for certain operations
|
||||||
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ struct Composite {
|
|||||||
int gravity;
|
int gravity;
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
|
bool hasOffset;
|
||||||
bool tile;
|
bool tile;
|
||||||
bool premultiplied;
|
bool premultiplied;
|
||||||
|
|
||||||
@@ -47,8 +48,9 @@ struct Composite {
|
|||||||
input(nullptr),
|
input(nullptr),
|
||||||
mode(VIPS_BLEND_MODE_OVER),
|
mode(VIPS_BLEND_MODE_OVER),
|
||||||
gravity(0),
|
gravity(0),
|
||||||
left(-1),
|
left(0),
|
||||||
top(-1),
|
top(0),
|
||||||
|
hasOffset(false),
|
||||||
tile(false),
|
tile(false),
|
||||||
premultiplied(false) {}
|
premultiplied(false) {}
|
||||||
};
|
};
|
||||||
@@ -79,6 +81,7 @@ struct PipelineBaton {
|
|||||||
int cropOffsetLeft;
|
int cropOffsetLeft;
|
||||||
int cropOffsetTop;
|
int cropOffsetTop;
|
||||||
bool premultiplied;
|
bool premultiplied;
|
||||||
|
bool tileCentre;
|
||||||
std::string kernel;
|
std::string kernel;
|
||||||
bool fastShrinkOnLoad;
|
bool fastShrinkOnLoad;
|
||||||
double tintA;
|
double tintA;
|
||||||
@@ -118,6 +121,13 @@ struct PipelineBaton {
|
|||||||
int extendRight;
|
int extendRight;
|
||||||
std::vector<double> extendBackground;
|
std::vector<double> extendBackground;
|
||||||
bool withoutEnlargement;
|
bool withoutEnlargement;
|
||||||
|
std::vector<double> affineMatrix;
|
||||||
|
std::vector<double> affineBackground;
|
||||||
|
double affineIdx;
|
||||||
|
double affineIdy;
|
||||||
|
double affineOdx;
|
||||||
|
double affineOdy;
|
||||||
|
vips::VInterpolate affineInterpolator;
|
||||||
int jpegQuality;
|
int jpegQuality;
|
||||||
bool jpegProgressive;
|
bool jpegProgressive;
|
||||||
std::string jpegChromaSubsampling;
|
std::string jpegChromaSubsampling;
|
||||||
@@ -151,6 +161,7 @@ struct PipelineBaton {
|
|||||||
double tiffYres;
|
double tiffYres;
|
||||||
int heifQuality;
|
int heifQuality;
|
||||||
VipsForeignHeifCompression heifCompression;
|
VipsForeignHeifCompression heifCompression;
|
||||||
|
int heifSpeed;
|
||||||
bool heifLossless;
|
bool heifLossless;
|
||||||
std::string err;
|
std::string err;
|
||||||
bool withMetadata;
|
bool withMetadata;
|
||||||
@@ -230,6 +241,13 @@ struct PipelineBaton {
|
|||||||
extendRight(0),
|
extendRight(0),
|
||||||
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
|
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
withoutEnlargement(false),
|
withoutEnlargement(false),
|
||||||
|
affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
|
||||||
|
affineBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
affineIdx(0),
|
||||||
|
affineIdy(0),
|
||||||
|
affineOdx(0),
|
||||||
|
affineOdy(0),
|
||||||
|
affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
|
||||||
jpegQuality(80),
|
jpegQuality(80),
|
||||||
jpegProgressive(false),
|
jpegProgressive(false),
|
||||||
jpegChromaSubsampling("4:2:0"),
|
jpegChromaSubsampling("4:2:0"),
|
||||||
@@ -261,8 +279,9 @@ struct PipelineBaton {
|
|||||||
tiffTileWidth(256),
|
tiffTileWidth(256),
|
||||||
tiffXres(1.0),
|
tiffXres(1.0),
|
||||||
tiffYres(1.0),
|
tiffYres(1.0),
|
||||||
heifQuality(80),
|
heifQuality(50),
|
||||||
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
|
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
||||||
|
heifSpeed(5),
|
||||||
heifLossless(false),
|
heifLossless(false),
|
||||||
withMetadata(false),
|
withMetadata(false),
|
||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
|
|
||||||
static void* sharp_vips_init(void*) {
|
static void* sharp_vips_init(void*) {
|
||||||
|
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
||||||
|
g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
|
||||||
vips_init("sharp");
|
vips_init("sharp");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/stats.cc
@@ -80,12 +80,14 @@ class StatsWorker : public Napi::AsyncWorker {
|
|||||||
// Estimate entropy via histogram of greyscale value frequency
|
// Estimate entropy via histogram of greyscale value frequency
|
||||||
baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
|
baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
|
||||||
// Estimate sharpness via standard deviation of greyscale laplacian
|
// Estimate sharpness via standard deviation of greyscale laplacian
|
||||||
VImage laplacian = VImage::new_matrixv(3, 3,
|
if (image.width() > 1 || image.height() > 1) {
|
||||||
0.0, 1.0, 0.0,
|
VImage laplacian = VImage::new_matrixv(3, 3,
|
||||||
1.0, -4.0, 1.0,
|
0.0, 1.0, 0.0,
|
||||||
0.0, 1.0, 0.0);
|
1.0, -4.0, 1.0,
|
||||||
laplacian.set("scale", 9.0);
|
0.0, 1.0, 0.0);
|
||||||
baton->sharpness = greyscale.conv(laplacian).deviate();
|
laplacian.set("scale", 9.0);
|
||||||
|
baton->sharpness = greyscale.conv(laplacian).deviate();
|
||||||
|
}
|
||||||
// Most dominant sRGB colour via 4096-bin 3D histogram
|
// Most dominant sRGB colour via 4096-bin 3D histogram
|
||||||
vips::VImage hist = sharp::RemoveAlpha(image)
|
vips::VImage hist = sharp::RemoveAlpha(image)
|
||||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
"test": "node perf && node random && node parallel"
|
"test": "node perf && node random && node parallel"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^3.1.0",
|
"async": "3.2.0",
|
||||||
"benchmark": "^2.1.4",
|
"benchmark": "2.1.4",
|
||||||
"gm": "^1.23.1",
|
"gm": "1.23.1",
|
||||||
"imagemagick": "^0.1.3",
|
"imagemagick": "0.1.3",
|
||||||
"jimp": "^0.16.0",
|
"jimp": "0.16.1",
|
||||||
"mapnik": "^4.5.2",
|
"mapnik": "4.5.5",
|
||||||
"semver": "^7.1.1"
|
"semver": "7.3.4"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.16.0"
|
"node": "14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
test/fixtures/big-height.webp
vendored
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
3
test/fixtures/circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
|
||||||
|
<circle r="3.75" cx="4" cy="4" fill="deeppink" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 122 B |
BIN
test/fixtures/cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
test/fixtures/expected/affine-background-all-offsets-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
test/fixtures/expected/affine-background-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
test/fixtures/expected/affine-background-output-offsets-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
test/fixtures/expected/affine-bicubic-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
test/fixtures/expected/affine-bilinear-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
test/fixtures/expected/affine-extract-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
test/fixtures/expected/affine-extract-rotate-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
test/fixtures/expected/affine-lbb-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
test/fixtures/expected/affine-nearest-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
test/fixtures/expected/affine-nohalo-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
test/fixtures/expected/affine-resize-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
test/fixtures/expected/affine-rotate-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
test/fixtures/expected/affine-vsqbs-2x-upscale-expected.jpg
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
test/fixtures/expected/circle.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
test/fixtures/expected/overlay-negative-offset-with-gravity.jpg
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
test/fixtures/expected/tile_centered.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
test/fixtures/expected/tint-cmyk.jpg
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
3
test/fixtures/index.js
vendored
@@ -95,6 +95,7 @@ module.exports = {
|
|||||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||||
|
inputWebPAnimatedBigHeight: getPath('big-height.webp'),
|
||||||
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||||
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
|
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
|
||||||
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
|
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
|
||||||
@@ -106,7 +107,9 @@ module.exports = {
|
|||||||
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
|
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
|
||||||
inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
|
inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
|
||||||
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
|
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
|
||||||
|
inputSvgSmallViewBox: getPath('circle.svg'),
|
||||||
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
|
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
|
||||||
|
inputAvif: getPath('cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
|
||||||
|
|
||||||
inputJPGBig: getPath('flowers.jpeg'),
|
inputJPGBig: getPath('flowers.jpeg'),
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,16 @@
|
|||||||
Memcheck:Cond
|
Memcheck:Cond
|
||||||
obj:*/libjpeg.so*
|
obj:*/libjpeg.so*
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
value_jpeg_obj_static
|
||||||
|
Memcheck:Value8
|
||||||
|
obj:*/libvips.so*
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_jpeg_obj_static
|
||||||
|
Memcheck:Cond
|
||||||
|
obj:*/libvips.so*
|
||||||
|
}
|
||||||
{
|
{
|
||||||
param_jpeg_jpeg_finish_compress
|
param_jpeg_jpeg_finish_compress
|
||||||
Memcheck:Param
|
Memcheck:Param
|
||||||
@@ -179,6 +189,24 @@
|
|||||||
...
|
...
|
||||||
fun:gsf_output_write
|
fun:gsf_output_write
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
param_gsf_new_do_write
|
||||||
|
Memcheck:Param
|
||||||
|
write(buf)
|
||||||
|
...
|
||||||
|
fun:new_do_write
|
||||||
|
...
|
||||||
|
fun:gsf_output_close
|
||||||
|
}
|
||||||
|
{
|
||||||
|
param_gsf_output_write
|
||||||
|
Memcheck:Param
|
||||||
|
write(buf)
|
||||||
|
...
|
||||||
|
fun:new_do_write
|
||||||
|
...
|
||||||
|
fun:gsf_output_write
|
||||||
|
}
|
||||||
|
|
||||||
# fontconfig
|
# fontconfig
|
||||||
{
|
{
|
||||||
@@ -208,6 +236,84 @@
|
|||||||
fun:XML_ParseBuffer
|
fun:XML_ParseBuffer
|
||||||
obj:*/libfontconfig.so.*
|
obj:*/libfontconfig.so.*
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
leak_fontconfig_FcInitLoadConfigAndFonts
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:XML_ParseBuffer
|
||||||
|
...
|
||||||
|
fun:FcInitLoadConfigAndFonts
|
||||||
|
}
|
||||||
|
|
||||||
|
# heif
|
||||||
|
{
|
||||||
|
cond_heif_encode_image
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:heif_context_encode_image
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_heif_encode_image
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:heif_context_encode_image
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_heif_aom_codec_encode
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:aom_codec_encode
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_heif_aom_codec_encode
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:aom_codec_encode
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value1_heif_aom_codec_encode
|
||||||
|
Memcheck:Value1
|
||||||
|
...
|
||||||
|
fun:aom_codec_encode
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_heif_av1_encode_frame
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:av1_encode_frame
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_heif_av1_encode_frame
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:av1_encode_frame
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_heif_context_write
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:heif_context_write
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_heif_context_write
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:heif_context_write
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_heif_context_read
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:heif_context_read_from_reader
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_heif_context_read
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:heif_context_read_from_reader
|
||||||
|
}
|
||||||
|
|
||||||
# libvips
|
# libvips
|
||||||
{
|
{
|
||||||
@@ -296,6 +402,30 @@
|
|||||||
...
|
...
|
||||||
fun:start_thread
|
fun:start_thread
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
cond_libvips_source_read
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:vips_source_read
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_libvips_source_read
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:vips_source_read
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_libvips_target_finish
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:vips_target_finish
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value8_libvips_target_finish
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:vips_target_finish
|
||||||
|
}
|
||||||
{
|
{
|
||||||
leak_libvips_init
|
leak_libvips_init
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@@ -304,6 +434,42 @@
|
|||||||
...
|
...
|
||||||
fun:vips__init
|
fun:vips__init
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
leak_rsvg_static_data
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:rsvg_rust_handle_new_from_stream_sync
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_rsvg_rsvg_rust_handle_new_from_gfile_sync
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:rsvg_rust_handle_new_from_gfile_sync
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_rsvg_rust_handle_new_from_stream_sync
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:xmlParseElement
|
||||||
|
...
|
||||||
|
fun:rsvg_rust_handle_new_from_stream_sync
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_rsvg_rust_handle_new_from_gfile_sync
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:xmlParseElement
|
||||||
|
...
|
||||||
|
fun:rsvg_rust_handle_new_from_gfile_sync
|
||||||
|
}
|
||||||
|
|
||||||
# libuv warnings
|
# libuv warnings
|
||||||
{
|
{
|
||||||
@@ -418,6 +584,13 @@
|
|||||||
...
|
...
|
||||||
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
|
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_CreateEnvironment_IsolateData
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:_ZN4node17CreateEnvironmentEPNS_11IsolateDataEN2v85LocalINS2_7ContextEEERKSt6vectorISsSaISsEESA_NS_16EnvironmentFlags5FlagsENS_8ThreadIdESt10unique_ptrINS_21InspectorParentHandleESt14default_deleteISF_EE
|
||||||
|
}
|
||||||
{
|
{
|
||||||
leak_nodejs_Environment_Start
|
leak_nodejs_Environment_Start
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@@ -664,3 +837,27 @@
|
|||||||
...
|
...
|
||||||
fun:_ZN12v8_inspector10toString16ERKNS_10StringViewE
|
fun:_ZN12v8_inspector10toString16ERKNS_10StringViewE
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
cond_v8_Builtins_InterpreterEntryTrampoline
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:Builtins_InterpreterEntryTrampoline
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ZN4node11Environment27RunAndClearNativeImmediatesEb
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:_ZN4node11Environment27RunAndClearNativeImmediatesEb
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_v8_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
|
||||||
|
}
|
||||||
|
|||||||
175
test/unit/affine.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('Affine transform', () => {
|
||||||
|
describe('Invalid input', () => {
|
||||||
|
it('Missing matrix', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid 1d matrix', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine(['123', 123, 123, 123]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid 2d matrix', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[123, 123], [null, 123]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid options parameter type', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[1, 0], [0, 1]], 'invalid options type');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid background color', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([4, 4, 4, 4], { background: 'not a color' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid idx offset type', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[4, 4], [4, 4]], { idx: 'invalid idx type' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid idy offset type', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([4, 4, 4, 4], { idy: 'invalid idy type' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid odx offset type', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[4, 4], [4, 4]], { odx: 'invalid odx type' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid ody offset type', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[4, 4], [4, 4]], { ody: 'invalid ody type' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Invalid interpolator', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.affine([[4, 4], [4, 4]], { interpolator: 'cubic' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Applies identity matrix', done => {
|
||||||
|
const input = fixtures.inputJpg;
|
||||||
|
sharp(input)
|
||||||
|
.affine([[1, 0], [0, 1]])
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(input, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Applies resize affine matrix', done => {
|
||||||
|
const input = fixtures.inputJpg;
|
||||||
|
const inputWidth = 2725;
|
||||||
|
const inputHeight = 2225;
|
||||||
|
sharp(input)
|
||||||
|
.affine([[0.2, 0], [0, 1.5]])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(input, data, done);
|
||||||
|
assert.strictEqual(info.width, Math.ceil(inputWidth * 0.2));
|
||||||
|
assert.strictEqual(info.height, Math.ceil(inputHeight * 1.5));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Resizes and applies affine transform', done => {
|
||||||
|
const input = fixtures.inputJpg;
|
||||||
|
sharp(input)
|
||||||
|
.resize(500, 500)
|
||||||
|
.affine([[0.5, 1], [1, 0.5]])
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(data, fixtures.expected('affine-resize-expected.jpg'), done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Extracts and applies affine transform', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract({ left: 300, top: 300, width: 600, height: 600 })
|
||||||
|
.affine([0.3, 0, -0.5, 0.3])
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(data, fixtures.expected('affine-extract-expected.jpg'), done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Rotates and applies affine transform', done => {
|
||||||
|
sharp(fixtures.inputJpg320x240)
|
||||||
|
.rotate(90)
|
||||||
|
.affine([[-1.2, 0], [0, -1.2]])
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(data, fixtures.expected('affine-rotate-expected.jpg'), done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Extracts, rotates and applies affine transform', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract({ left: 1000, top: 1000, width: 200, height: 200 })
|
||||||
|
.rotate(45, { background: 'blue' })
|
||||||
|
.affine([[2, 1], [2, -0.5]], { background: 'red' })
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(fixtures.expected('affine-extract-rotate-expected.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Applies affine transform with background color', done => {
|
||||||
|
sharp(fixtures.inputJpg320x240)
|
||||||
|
.rotate(180)
|
||||||
|
.affine([[-1.5, 1.2], [-1, 1]], { background: 'red' })
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(fixtures.expected('affine-background-expected.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Applies affine transform with background color and output offsets', done => {
|
||||||
|
sharp(fixtures.inputJpg320x240)
|
||||||
|
.rotate(180)
|
||||||
|
.affine([[-2, 1.5], [-1, 2]], { background: 'blue', odx: 40, ody: -100 })
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(fixtures.expected('affine-background-output-offsets-expected.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Applies affine transform with background color and all offsets', done => {
|
||||||
|
sharp(fixtures.inputJpg320x240)
|
||||||
|
.rotate(180)
|
||||||
|
.affine([[-1.2, 1.8], [-1, 2]], { background: 'yellow', idx: 10, idy: -40, odx: 10, ody: -50 })
|
||||||
|
.toBuffer((err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
fixtures.assertSimilar(fixtures.expected('affine-background-all-offsets-expected.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Interpolations', () => {
|
||||||
|
const input = fixtures.inputJpg320x240;
|
||||||
|
const inputWidth = 320;
|
||||||
|
const inputHeight = 240;
|
||||||
|
for (const interp in sharp.interpolators) {
|
||||||
|
it(`Performs 2x upscale with ${interp} interpolation`, done => {
|
||||||
|
sharp(input)
|
||||||
|
.affine([[2, 0], [0, 2]], { interpolator: sharp.interpolators[interp] })
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(info.width, Math.ceil(inputWidth * 2));
|
||||||
|
assert.strictEqual(info.height, Math.ceil(inputHeight * 2));
|
||||||
|
fixtures.assertSimilar(fixtures.expected(`affine-${sharp.interpolators[interp]}-2x-upscale-expected.jpg`), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
84
test/unit/avif.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const { inputAvif, inputJpg } = require('../fixtures');
|
||||||
|
|
||||||
|
describe('AVIF', () => {
|
||||||
|
it('called without options does not throw an error', () => {
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
sharp().avif();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can passthrough AVIF', async () => {
|
||||||
|
const data = await sharp(inputAvif)
|
||||||
|
.resize(32)
|
||||||
|
.toBuffer();
|
||||||
|
const metadata = await sharp(data)
|
||||||
|
.metadata();
|
||||||
|
const { size, ...metadataWithoutSize } = metadata;
|
||||||
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
|
channels: 3,
|
||||||
|
depth: 'uchar',
|
||||||
|
format: 'heif',
|
||||||
|
hasAlpha: false,
|
||||||
|
hasProfile: false,
|
||||||
|
height: 12,
|
||||||
|
isProgressive: false,
|
||||||
|
pageHeight: 12,
|
||||||
|
pagePrimary: 0,
|
||||||
|
pages: 1,
|
||||||
|
space: 'srgb',
|
||||||
|
width: 32
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can convert AVIF to JPEG', async () => {
|
||||||
|
const data = await sharp(inputAvif)
|
||||||
|
.resize(32)
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer();
|
||||||
|
const metadata = await sharp(data)
|
||||||
|
.metadata();
|
||||||
|
const { size, ...metadataWithoutSize } = metadata;
|
||||||
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
|
channels: 3,
|
||||||
|
chromaSubsampling: '4:2:0',
|
||||||
|
density: 72,
|
||||||
|
depth: 'uchar',
|
||||||
|
format: 'jpeg',
|
||||||
|
hasAlpha: false,
|
||||||
|
hasProfile: false,
|
||||||
|
height: 13,
|
||||||
|
isProgressive: false,
|
||||||
|
space: 'srgb',
|
||||||
|
width: 32
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can convert JPEG to AVIF', async () => {
|
||||||
|
const data = await sharp(inputJpg)
|
||||||
|
.resize(32)
|
||||||
|
.avif()
|
||||||
|
.toBuffer();
|
||||||
|
const metadata = await sharp(data)
|
||||||
|
.metadata();
|
||||||
|
const { size, ...metadataWithoutSize } = metadata;
|
||||||
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
|
channels: 3,
|
||||||
|
depth: 'uchar',
|
||||||
|
format: 'heif',
|
||||||
|
hasAlpha: false,
|
||||||
|
hasProfile: false,
|
||||||
|
height: 26,
|
||||||
|
isProgressive: false,
|
||||||
|
pageHeight: 26,
|
||||||
|
pagePrimary: 0,
|
||||||
|
pages: 1,
|
||||||
|
space: 'srgb',
|
||||||
|
width: 32
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -172,6 +172,24 @@ describe('composite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('negative offset and gravity', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
left: -10,
|
||||||
|
top: -10,
|
||||||
|
gravity: 4
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(
|
||||||
|
fixtures.expected('overlay-negative-offset-with-gravity.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('offset, gravity and tile', done => {
|
it('offset, gravity and tile', done => {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(80)
|
.resize(80)
|
||||||
@@ -333,13 +351,25 @@ describe('composite', () => {
|
|||||||
it('invalid left', () => {
|
it('invalid left', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().composite([{ input: 'test', left: 0.5 }]);
|
sharp().composite([{ input: 'test', left: 0.5 }]);
|
||||||
}, /Expected positive integer for left but received 0.5 of type number/);
|
}, /Expected integer for left but received 0.5 of type number/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 'invalid' }]);
|
||||||
|
}, /Expected integer for left but received invalid of type string/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 'invalid', top: 10 }]);
|
||||||
|
}, /Expected integer for left but received invalid of type string/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invalid top', () => {
|
it('invalid top', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().composite([{ input: 'test', top: -1 }]);
|
sharp().composite([{ input: 'test', top: 0.5 }]);
|
||||||
}, /Expected positive integer for top but received -1 of type number/);
|
}, /Expected integer for top but received 0.5 of type number/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: 'invalid' }]);
|
||||||
|
}, /Expected integer for top but received invalid of type string/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: 'invalid', left: 10 }]);
|
||||||
|
}, /Expected integer for top but received invalid of type string/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('left but no top', () => {
|
it('left but no top', () => {
|
||||||
|
|||||||
@@ -98,4 +98,36 @@ describe('GIF input', () => {
|
|||||||
sharp().gif({ delay: [65536] });
|
sharp().gif({ delay: [65536] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with streams when only animated is set', function (done) {
|
||||||
|
if (sharp.format.magick.output.buffer) {
|
||||||
|
fs.createReadStream(fixtures.inputGifAnimated)
|
||||||
|
.pipe(sharp({ animated: true }))
|
||||||
|
.gif()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('gif', info.format);
|
||||||
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with streams when only pages is set', function (done) {
|
||||||
|
if (sharp.format.magick.output.buffer) {
|
||||||
|
fs.createReadStream(fixtures.inputGifAnimated)
|
||||||
|
.pipe(sharp({ pages: -1 }))
|
||||||
|
.gif()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('gif', info.format);
|
||||||
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,76 +4,65 @@ const assert = require('assert');
|
|||||||
|
|
||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
|
|
||||||
const formatHeifOutputBuffer = sharp.format.heif.output.buffer;
|
describe('HEIF', () => {
|
||||||
|
it('called without options does not throw an error', () => {
|
||||||
describe('HEIF (experimental)', () => {
|
assert.doesNotThrow(() => {
|
||||||
describe('Stubbed without support for HEIF', () => {
|
sharp().heif();
|
||||||
before(() => {
|
|
||||||
sharp.format.heif.output.buffer = false;
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error', () => {
|
|
||||||
assert.throws(() => {
|
|
||||||
sharp().heif();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('valid quality does not throw an error', () => {
|
||||||
describe('Stubbed with support for HEIF', () => {
|
assert.doesNotThrow(() => {
|
||||||
before(() => {
|
sharp().heif({ quality: 80 });
|
||||||
sharp.format.heif.output.buffer = true;
|
|
||||||
});
|
});
|
||||||
after(() => {
|
});
|
||||||
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
|
it('invalid quality should throw an error', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().heif({ quality: 101 });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
it('called without options does not throw an error', () => {
|
it('non-numeric quality should throw an error', () => {
|
||||||
assert.doesNotThrow(() => {
|
assert.throws(() => {
|
||||||
sharp().heif();
|
sharp().heif({ quality: 'fail' });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('valid quality does not throw an error', () => {
|
});
|
||||||
assert.doesNotThrow(() => {
|
it('valid lossless does not throw an error', () => {
|
||||||
sharp().heif({ quality: 50 });
|
assert.doesNotThrow(() => {
|
||||||
});
|
sharp().heif({ lossless: true });
|
||||||
});
|
});
|
||||||
it('invalid quality should throw an error', () => {
|
});
|
||||||
assert.throws(() => {
|
it('non-boolean lossless should throw an error', () => {
|
||||||
sharp().heif({ quality: 101 });
|
assert.throws(() => {
|
||||||
});
|
sharp().heif({ lossless: 'fail' });
|
||||||
});
|
});
|
||||||
it('non-numeric quality should throw an error', () => {
|
});
|
||||||
assert.throws(() => {
|
it('valid compression does not throw an error', () => {
|
||||||
sharp().heif({ quality: 'fail' });
|
assert.doesNotThrow(() => {
|
||||||
});
|
sharp().heif({ compression: 'hevc' });
|
||||||
});
|
});
|
||||||
it('valid lossless does not throw an error', () => {
|
});
|
||||||
assert.doesNotThrow(() => {
|
it('unknown compression should throw an error', () => {
|
||||||
sharp().heif({ lossless: true });
|
assert.throws(() => {
|
||||||
});
|
sharp().heif({ compression: 'fail' });
|
||||||
});
|
});
|
||||||
it('non-boolean lossless should throw an error', () => {
|
});
|
||||||
assert.throws(() => {
|
it('invalid compression should throw an error', () => {
|
||||||
sharp().heif({ lossless: 'fail' });
|
assert.throws(() => {
|
||||||
});
|
sharp().heif({ compression: 1 });
|
||||||
});
|
});
|
||||||
it('valid compression does not throw an error', () => {
|
});
|
||||||
assert.doesNotThrow(() => {
|
it('valid speed does not throw an error', () => {
|
||||||
sharp().heif({ compression: 'avc' });
|
assert.doesNotThrow(() => {
|
||||||
});
|
sharp().heif({ speed: 6 });
|
||||||
});
|
});
|
||||||
it('unknown compression should throw an error', () => {
|
});
|
||||||
assert.throws(() => {
|
it('out of range speed should throw an error', () => {
|
||||||
sharp().heif({ compression: 'fail' });
|
assert.throws(() => {
|
||||||
});
|
sharp().heif({ speed: 9 });
|
||||||
});
|
});
|
||||||
it('invalid compression should throw an error', () => {
|
});
|
||||||
assert.throws(() => {
|
it('invalid speed should throw an error', () => {
|
||||||
sharp().heif({ compression: 1 });
|
assert.throws(() => {
|
||||||
});
|
sharp().heif({ compression: 'fail' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -302,7 +302,6 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Fail when input is empty Buffer', function (done) {
|
it('Fail when input is empty Buffer', function (done) {
|
||||||
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.0+
|
|
||||||
sharp(Buffer.alloc(0)).toBuffer().then(function () {
|
sharp(Buffer.alloc(0)).toBuffer().then(function () {
|
||||||
assert(false);
|
assert(false);
|
||||||
done();
|
done();
|
||||||
@@ -647,7 +646,7 @@ describe('Input/output', function () {
|
|||||||
it('Invalid density: string', function () {
|
it('Invalid density: string', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp({ density: 'zoinks' });
|
sharp({ density: 'zoinks' });
|
||||||
}, /Expected number between 1 and 2400 for density but received zoinks of type string/);
|
}, /Expected number between 1 and 100000 for density but received zoinks of type string/);
|
||||||
});
|
});
|
||||||
it('Setting animated property updates pages property', function () {
|
it('Setting animated property updates pages property', function () {
|
||||||
assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);
|
assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const sharp = require('../../');
|
|||||||
const fixtures = require('../fixtures');
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
// Test Helpers
|
// Test Helpers
|
||||||
var threshold = 0.001;
|
const threshold = 0.001;
|
||||||
function isInAcceptableRange (actual, expected) {
|
function isInAcceptableRange (actual, expected) {
|
||||||
return actual >= ((1 - threshold) * expected) && actual <= ((1 + threshold) * expected);
|
return actual >= ((1 - threshold) * expected) && actual <= ((1 + threshold) * expected);
|
||||||
}
|
}
|
||||||
@@ -279,10 +279,10 @@ describe('Image Stats', function () {
|
|||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(true, isInRange(stats.channels[0].max, 254, 255));
|
assert.strictEqual(true, isInRange(stats.channels[0].max, 254, 255));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 82506996));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 83291370));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11213984832));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11379783198));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 104.36947963892487));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 105.36169496842616));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.379896254993135));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.39412151419967));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[0].minX));
|
assert.strictEqual(true, isInteger(stats.channels[0].minX));
|
||||||
assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 1024));
|
assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 1024));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[0].minY));
|
assert.strictEqual(true, isInteger(stats.channels[0].minY));
|
||||||
@@ -295,10 +295,10 @@ describe('Image Stats', function () {
|
|||||||
// green channel
|
// green channel
|
||||||
assert.strictEqual(0, stats.channels[1].min);
|
assert.strictEqual(0, stats.channels[1].min);
|
||||||
assert.strictEqual(true, isInRange(stats.channels[1].max, 254, 255));
|
assert.strictEqual(true, isInRange(stats.channels[1].max, 254, 255));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120089056));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120877425));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20533721114));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20774687595));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 151.90993361398964));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 152.9072025279307));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.83370206587037));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.84143349689916));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[1].minX));
|
assert.strictEqual(true, isInteger(stats.channels[1].minX));
|
||||||
assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 1024));
|
assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 1024));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[1].minY));
|
assert.strictEqual(true, isInteger(stats.channels[1].minY));
|
||||||
@@ -311,10 +311,10 @@ describe('Image Stats', function () {
|
|||||||
// blue channel
|
// blue channel
|
||||||
assert.strictEqual(0, stats.channels[2].min);
|
assert.strictEqual(0, stats.channels[2].min);
|
||||||
assert.strictEqual(true, isInRange(stats.channels[2].max, 254, 255));
|
assert.strictEqual(true, isInRange(stats.channels[2].max, 254, 255));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138153653));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138938859));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28172033081));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28449125593));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 174.76123932359133));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 175.75450711423252));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.38276338513747));
|
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.39929031070358));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[2].minX));
|
assert.strictEqual(true, isInteger(stats.channels[2].minX));
|
||||||
assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 1024));
|
assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 1024));
|
||||||
assert.strictEqual(true, isInteger(stats.channels[2].minY));
|
assert.strictEqual(true, isInteger(stats.channels[2].minY));
|
||||||
@@ -462,6 +462,19 @@ describe('Image Stats', function () {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('Entropy and sharpness of 1x1 input are zero', async () => {
|
||||||
|
const { entropy, sharpness } = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
channels: 3,
|
||||||
|
background: 'red'
|
||||||
|
}
|
||||||
|
}).stats();
|
||||||
|
assert.strictEqual(entropy, 0);
|
||||||
|
assert.strictEqual(sharpness, 0);
|
||||||
|
});
|
||||||
|
|
||||||
it('Stream in, Callback out', function (done) {
|
it('Stream in, Callback out', function (done) {
|
||||||
const readable = fs.createReadStream(fixtures.inputJpg);
|
const readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
const pipeline = sharp().stats(function (err, stats) {
|
const pipeline = sharp().stats(function (err, stats) {
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ describe('SVG input', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Convert SVG to PNG at DPI larger than 2400', function (done) {
|
||||||
|
const size = 1024;
|
||||||
|
sharp(fixtures.inputSvgSmallViewBox).metadata(function (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) {
|
||||||
|
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) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(data).metadata(function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(9216, info.density);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Convert SVG to PNG at 14.4DPI', function (done) {
|
it('Convert SVG to PNG at 14.4DPI', function (done) {
|
||||||
sharp(fixtures.inputSvg, { density: 14.4 })
|
sharp(fixtures.inputSvg, { density: 14.4 })
|
||||||
.toFormat('png')
|
.toFormat('png')
|
||||||
|
|||||||
@@ -161,9 +161,7 @@ describe('TIFF', function () {
|
|||||||
.then(() => sharp(fixtures.outputTiff)
|
.then(() => sharp(fixtures.outputTiff)
|
||||||
.metadata()
|
.metadata()
|
||||||
.then(({ density }) => {
|
.then(({ density }) => {
|
||||||
assert.strictEqual(true,
|
assert.strictEqual(25400, density);
|
||||||
density === 2540 || // libvips <= 8.8.2
|
|
||||||
density === 25400); // libvips >= 8.8.3
|
|
||||||
return promisify(rimraf)(fixtures.outputTiff);
|
return promisify(rimraf)(fixtures.outputTiff);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -179,9 +177,7 @@ describe('TIFF', function () {
|
|||||||
.then(data => sharp(data)
|
.then(data => sharp(data)
|
||||||
.metadata()
|
.metadata()
|
||||||
.then(({ density }) => {
|
.then(({ density }) => {
|
||||||
assert.strictEqual(true,
|
assert.strictEqual(25400, density);
|
||||||
density === 2540 || // libvips <= 8.8.2
|
|
||||||
density === 25400); // libvips >= 8.8.3
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -289,6 +289,14 @@ describe('Tile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Invalid center parameter value fail', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().tile({
|
||||||
|
centre: 'true'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Deep Zoom layout', function (done) {
|
it('Deep Zoom layout', function (done) {
|
||||||
const directory = fixtures.path('output.dzi_files');
|
const directory = fixtures.path('output.dzi_files');
|
||||||
rimraf(directory, function () {
|
rimraf(directory, function () {
|
||||||
@@ -765,6 +773,46 @@ describe('Tile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Google layout with center image in tile', function (done) {
|
||||||
|
const directory = fixtures.path('output.google_center.dzi');
|
||||||
|
rimraf(directory, function () {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.tile({
|
||||||
|
center: true,
|
||||||
|
layout: 'google'
|
||||||
|
})
|
||||||
|
.toFile(directory, function (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);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('tile_centered.jpg'), fs.readFileSync(path.join(directory, '0', '0', '0.jpg')), done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Google layout with center image in tile centre', function (done) {
|
||||||
|
const directory = fixtures.path('output.google_center.dzi');
|
||||||
|
rimraf(directory, function () {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.tile({
|
||||||
|
centre: true,
|
||||||
|
layout: 'google'
|
||||||
|
})
|
||||||
|
.toFile(directory, function (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);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('tile_centered.jpg'), fs.readFileSync(path.join(directory, '0', '0', '0.jpg')), done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('IIIF layout', function (done) {
|
it('IIIF layout', function (done) {
|
||||||
const directory = fixtures.path('output.iiif.info');
|
const directory = fixtures.path('output.iiif.info');
|
||||||
rimraf(directory, function () {
|
rimraf(directory, function () {
|
||||||
|
|||||||
@@ -16,4 +16,12 @@ describe('toBuffer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('correctly process animated webp with height > 16383', (done) => {
|
||||||
|
const image = sharp(fixtures.inputWebPAnimatedBigHeight, { animated: true });
|
||||||
|
image.toBuffer().then((buff) => {
|
||||||
|
assert.strictEqual(Buffer.isBuffer(buff), true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
@@ -184,4 +185,28 @@ describe('WebP', function () {
|
|||||||
|
|
||||||
assert.deepStrictEqual(updated.delay, expectedDelay);
|
assert.deepStrictEqual(updated.delay, expectedDelay);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with streams when only animated is set', function (done) {
|
||||||
|
fs.createReadStream(fixtures.inputWebPAnimated)
|
||||||
|
.pipe(sharp({ animated: true }))
|
||||||
|
.webp({ lossless: true })
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with streams when only pages is set', function (done) {
|
||||||
|
fs.createReadStream(fixtures.inputWebPAnimated)
|
||||||
|
.pipe(sharp({ pages: -1 }))
|
||||||
|
.webp({ lossless: true })
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||