Compare commits

...

97 Commits

Author SHA1 Message Date
Lovell Fuller
dad7f1e1f6 Release v0.25.3 2020-05-17 14:21:39 +01:00
Lovell Fuller
c3a9384dcf Upgrade deps that have dropped Node.js 8 support 2020-05-17 13:29:55 +01:00
Lovell Fuller
0ee08bfe46 Docs: lint JSDoc annotations when building docs 2020-05-15 14:07:27 +01:00
Lovell Fuller
0872f495f9 Tests: temporarily skip known failure on FreeBSD 2020-05-15 12:17:45 +01:00
Lovell Fuller
33ac8b93a9 CI: allow FreeBSD to update to latest packages 2020-05-13 16:12:17 +01:00
Lovell Fuller
bbff1c222d Docs: clarify description of withMetadata
Default and non-default behaviour was mixed in the same paragraph,
leading to confusion about when an ICC profile might be expected.
2020-05-13 16:01:03 +01:00
Lovell Fuller
b534f99870 Docs: correct typo in composite 2020-05-13 15:56:44 +01:00
Lovell Fuller
86acf2460e Allow parsing of SVG attribute values >10MB #2195 2020-05-06 20:56:08 +01:00
NickNaso
40c00035ee Docs: add N-API badge 2020-05-01 11:53:10 +01:00
Lovell Fuller
4673abae7d Docs: serve logo via CDN 2020-05-01 11:41:38 +01:00
Lovell Fuller
ef8a705957 Docs: move npm install command to examples 2020-05-01 11:38:09 +01:00
Lovell Fuller
48255fa009 Docs: serve logo via CDN 2020-05-01 11:27:00 +01:00
Lovell Fuller
ed6269bc9c Bump dependencies 2020-05-01 11:26:05 +01:00
Lovell Fuller
16b8d9afe5 Docs: changelog entry and credit for #2188 2020-05-01 11:25:43 +01:00
Dumitru
f29fcb1f73 Ensure npm platform flag is respected when copying DLLs 2020-04-29 18:24:27 +01:00
Lovell Fuller
b36ad25030 CI: Upgrade FreeBSD to version 13
It currently provides only vips v8.8.3 so will (still) fail.
2020-04-23 21:39:45 +01:00
Lovell Fuller
edf3fe24c9 CI: add Node.js 14 2020-04-23 21:30:04 +01:00
Lovell Fuller
5a1bdf0eb1 Bump deps: semver, documentation, nyc 2020-04-23 20:51:12 +01:00
Lovell Fuller
d8e24d7e76 Docs: slightly reduce whitespace before headings, lists 2020-04-23 20:47:10 +01:00
Floris de Bijl
786c5330e9 docs: change 'colourspace' to 'toColourspace' (#2174) 2020-04-21 09:44:22 +01:00
Lovell Fuller
70e730bb67 Docs: add basic search feature for install and API 2020-04-14 21:26:41 +01:00
Lovell Fuller
1d6ef630a5 Docs: extract might also be referred to as 'crop' 2020-04-14 21:24:26 +01:00
Vincent Voyer
1eedb22ef5 docs(clone): add promise example 2020-04-08 09:45:04 +01:00
Lovell Fuller
6ed1f49ad3 Ensure libvips is initialised only once #2143 2020-03-29 19:44:21 +01:00
Lovell Fuller
ecd01afad3 Docs: clarify JPEG quality/chromaSubsampling interdependence 2020-03-26 20:22:47 +00:00
Lovell Fuller
8bd8709f2b Release v0.25.2 2020-03-20 19:20:34 +00:00
Lovell Fuller
e82a585cec Ensure AsyncWorker options are persisted #2130 2020-03-19 21:21:21 +00:00
Lovell Fuller
c1d4a68558 Add changelog entry, docs and credit for #2098 2020-03-18 13:54:41 +00:00
Edward Silverton
eff36dc09f Add IIIF layout support to tile-based output (#2098) 2020-03-18 13:27:54 +00:00
Lovell Fuller
031a58f817 Docs: AWS Lambda cross-platform no longer requires target flag 2020-03-17 18:33:49 +00:00
Lovell Fuller
cf39fc4fb1 Simplify tiled layout JPEG file extension logic 2020-03-14 22:08:46 +00:00
Lovell Fuller
c9bff94e17 Docs: expand descriptions of resize cover/contain 2020-03-14 21:34:48 +00:00
Lovell Fuller
e78e919925 Clarify diff between install vs bug issue templates 2020-03-10 21:36:48 +00:00
Lovell Fuller
76bb25262e Docs: ARM64/RHEL7 prebuilt binaries changelog entry 2020-03-10 21:22:30 +00:00
Lovell Fuller
d8426b1ed5 CI: Switch Linux x64 to CentOS 7 for glibc 2.17, gcc 4.8.5
Expand Linux ARM64v8 to all supported versions of Node.js
2020-03-10 19:39:26 +00:00
Lovell Fuller
4894c10dd9 Ensure consistent, correct detection of input opts #2118 2020-03-08 14:32:17 +00:00
Lovell Fuller
24285bb0e0 Release v0.25.1 2020-03-07 21:21:44 +00:00
Lovell Fuller
bf75501262 Ensure prebuilt binary based on N-API version 2020-03-07 20:59:25 +00:00
Lovell Fuller
9e214a17f0 Release v0.25.1 2020-03-07 19:12:32 +00:00
Lovell Fuller
0aae1ecd9b Ensure prebuilt binaries use N-API v4 #2117 2020-03-07 18:52:44 +00:00
Lovell Fuller
062f990315 Release v0.25.0 2020-03-07 12:44:42 +00:00
Lovell Fuller
22685e44c0 Upgrade to libvips v8.9.1 2020-03-07 10:15:51 +00:00
Lovell Fuller
8d66433e7c Docs: 32-bit Windows is supported (again) 2020-03-06 21:33:15 +00:00
Lovell Fuller
82863f48f6 Update issue templates to ask common follow-up questions 2020-03-06 21:12:59 +00:00
Lovell Fuller
51b14329d6 Simplify and future-proof tile option parsing 2020-03-06 20:06:41 +00:00
Lovell Fuller
14dc7681ed Upgrade to libvips v8.9.0-alpha2 prebuild
Improve tests for 32-bit ARM
2020-03-04 23:17:23 +00:00
Lovell Fuller
258c9e86eb Improve docs relating to single-channel raw pixel output 2020-03-01 14:22:49 +00:00
Joe Flateau
03dad5f2b2 Docs: clarify stats.isOpaque (#2094) 2020-02-24 20:35:36 +00:00
Lovell Fuller
86264e7733 Docs: update Lambda section to use Node.js 12 2020-02-24 20:31:17 +00:00
Lovell Fuller
95eae905f0 Ensure limitInputPixels, sequentialRead for composite op #2099 2020-02-24 16:02:36 +00:00
Lovell Fuller
a90dbcc808 Docs: improve a11y and metadata 2020-02-24 09:24:27 +00:00
Lovell Fuller
e9b21f2211 Ensure correct ordering of rotate-then-trim ops #2087 2020-02-23 16:59:22 +00:00
Lovell Fuller
409e5174bb Add support for 32-bit Windows (win32-ia32) 2020-02-19 23:00:23 +00:00
Lovell Fuller
8b3c0daab2 Upgrade to libvips v8.9.1-alpha1 prebuild 2020-02-19 11:41:18 +00:00
Lovell Fuller
c17807c995 Remove previously deprecated limitInputPixels, sequentialRead 2020-02-15 20:01:46 +00:00
Lovell Fuller
4abb4edf64 Migrate internals to N-API #1282 2020-02-15 19:38:15 +00:00
Lovell Fuller
d5ecc537af Release v0.24.1 2020-02-15 13:48:46 +00:00
Lovell Fuller
c7a49054fd Docs: update libvips URLs for band format/interpretation 2020-02-15 13:43:47 +00:00
Lovell Fuller
a2314c4aa0 Ensure RGBA LZW TIFF info.channel count #2064 2020-02-15 11:46:13 +00:00
Lovell Fuller
1717173f17 Tests: tighten composite offset thresholds 2020-02-15 10:53:30 +00:00
Lovell Fuller
e44c12f029 Bump dependencies, tar is now node>=10 2020-02-15 10:53:00 +00:00
Lovell Fuller
1a98c390fc Prevent sequentialRead for EXIF-based rotate op #2042 2020-01-20 21:50:43 +00:00
Lovell Fuller
91902740e4 Attempt to detect out-of-date homebrew-managed vips 2020-01-19 19:58:24 +00:00
Lovell Fuller
6aa6a93b44 Docs: add details of ignore-scripts to installation guide 2020-01-19 19:27:50 +00:00
Lovell Fuller
b4135ac9b3 Docs: fix any remaining redirects 2020-01-16 20:57:02 +00:00
Lovell Fuller
78906e6551 Update any remaining documentation links 2020-01-16 20:52:19 +00:00
Lovell Fuller
ba29ba1ab7 Release v0.24.0 2020-01-16 19:11:34 +00:00
Lovell Fuller
e0fa15f5cb Update performance test results 2020-01-16 18:56:25 +00:00
Lovell Fuller
82a1ff359d Update dev and bench dependencies 2020-01-15 21:17:25 +00:00
Lovell Fuller
18e1f10a94 Add support for input with 16-bit RGB profile #2037 2020-01-15 21:16:11 +00:00
Lovell Fuller
4828a17643 Tests: add fontconfig static data leak suppression 2020-01-15 21:08:45 +00:00
Lovell Fuller
3b4f95597a Prevent use of sequentialRead for rotate ops #2016 2020-01-14 08:34:54 +00:00
Lovell Fuller
bd52e93fca Deprecate limitInputPixels and sequentialRead, move to input options 2020-01-12 19:59:39 +00:00
Lovell Fuller
6fdc79d569 Docs: ARM64 has prebuilt libvips, not sharp 2020-01-12 10:50:14 +00:00
Lovell Fuller
7dbad7206e Drop support for undef input where opts also provided #1768 2020-01-11 12:02:31 +00:00
Lovell Fuller
a8a0c1e935 Update doc links contained within code
Throw rather than exit if require fails
2020-01-10 16:29:40 +00:00
Lovell Fuller
00bcf60566 Docs: metadata delay/loop properties 2020-01-09 23:04:06 +00:00
Lovell Fuller
4a745f2d2e Expose delay/loop metadata for animated images #1905 2020-01-09 22:51:08 +00:00
Lovell Fuller
057074b238 Docs: improve header layout on narrow screen devices 2020-01-08 23:13:33 +00:00
Lovell Fuller
4b8cc13a05 Add 2020 to list of copyright years 2020-01-08 21:20:53 +00:00
Lovell Fuller
a4e8586b59 Docs: rewrite installation guide 2020-01-08 21:04:16 +00:00
Lovell Fuller
095b37db6a Rename OS X as macOS
Bump CI to latest xcode available on maxOS 10.13
2020-01-08 21:03:50 +00:00
Lovell Fuller
efff650314 Upgrade to libvips v8.9.0 2020-01-08 16:18:43 +00:00
Lovell Fuller
0764ca8fde Changelog entry and credit for #2024 2020-01-08 16:04:42 +00:00
Brychan
403160434b Correctly bind max width and height values when using withoutEnlargement (#2024) 2020-01-07 12:10:35 +00:00
Lovell Fuller
730ab14faa Docs: correct SVG logo URL 2020-01-06 17:51:11 +00:00
Lovell Fuller
51d70c4574 Docs: switch hosting from Readthedocs to Firebase 2020-01-06 14:48:35 +00:00
Lovell Fuller
765ccd2aaa Docs: add icons 2020-01-06 14:44:07 +00:00
Lovell Fuller
5399332f81 Docs: improve section nesting levels 2020-01-06 14:26:54 +00:00
Lovell Fuller
43b7f9fc0e Docs: inject API section name as title 2020-01-04 11:11:27 +00:00
Sebin Benjamin
3925689435 Docs: initial setup for docute documentation site generator 2020-01-04 10:59:08 +00:00
Lovell Fuller
96a994a4c0 Move functions to improve logical ordering of docs 2020-01-03 20:58:57 +00:00
Lovell Fuller
84c20373ec Update leak test suppressions 2020-01-03 20:47:01 +00:00
Lovell Fuller
a216d2945b Upgrade to libvips v8.9.0-rc4, drop support for Node.js 8 2020-01-03 20:26:55 +00:00
Lovell Fuller
755a0caf3d Fix 16-bit, 2-channel PNG w/ ICC profile support #2013 2019-12-20 17:19:33 +00:00
Lovell Fuller
703d90e663 Docs: add example of using metadata and resize to scale 2019-12-16 21:15:49 +00:00
Lovell Fuller
e230ce41d7 Docs: remove references to unmaintained Docker images 2019-12-16 21:07:31 +00:00
92 changed files with 3381 additions and 2112 deletions

View File

@@ -1,12 +1,13 @@
freebsd_instance:
image_family: freebsd-12-0
image_family: freebsd-13-0-snap
task:
env:
IGNORE_OSVERSION: yes
prerequisites_script:
- sed -i '' 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf
- pkg update -f
- pkg upgrade -y
- pkg install -y pkgconf vips libnghttp2 node npm
- pkg install -y pkgconf vips node npm
install_script:
- npm install --unsafe-perm
test_script:

View File

@@ -44,8 +44,7 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.24.0 | wit |
| v0.25.0 | yield |
| v0.26.0 | zoom |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
@@ -71,7 +70,7 @@ The public API is documented with [JSDoc](http://usejsdoc.org/) annotated commen
These can be converted to Markdown by running:
```sh
npm run docs
npm run docs-build
```
Please include documentation updates in any Pull Request that modifies the public API.

View File

@@ -1,9 +1,7 @@
---
name: Feature request
about: Suggest an idea
title: ''
labels: enhancement
assignees: ''
---

View File

@@ -1,18 +1,20 @@
---
name: Installation
about: Something went wrong **installing** sharp
title: ''
about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
labels: installation
assignees: ''
---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/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?
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`?
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,20 +1,20 @@
---
name: Possible bug
about: Something unexpected occurred **using** sharp
title: ''
about: Installation of sharp was successful but then something unexpected occurred using one of its features
labels: triage
assignees: ''
---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
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`?
What are the steps to reproduce?
What is the expected behaviour?
Are you able to provide a standalone code sample, without other dependencies, that demonstrates this problem?
Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
Are you able to provide a sample image that helps explain the problem?
What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,9 +1,7 @@
---
name: Question
about: For help understanding an existing feature
title: ''
labels: question
assignees: ''
---
@@ -13,6 +11,6 @@ What are you trying to achieve?
Have you searched for similar questions?
Are you able to provide a standalone code sample that demonstrates this question?
Are you able to provide a minimal, standalone code sample that demonstrates this question?
Are you able to provide a sample image that helps explain the question?

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ vendor
.vscode/
package-lock.json
.idea
.firebase

View File

@@ -1,91 +1,178 @@
matrix:
jobs:
include:
- name: "Linux (glibc) - Node 8"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "8"
- name: "Linux (glibc) - Node 10"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "10"
- name: "Linux (glibc) - Node 12"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "12"
- name: "Linux (glibc) - Node 13"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "13"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (musl) - Node 8"
os: linux
dist: trusty
sudo: true
language: minimal
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:8-alpine
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 --env PREBUILD_TOKEN --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 13"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_13.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 --env PREBUILD_TOKEN --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 PREBUILD_TOKEN --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 (musl) - Node 10"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10-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 (musl) - Node 12"
os: linux
dist: trusty
sudo: true
language: minimal
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 (musl) - Node 13"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 13"
os: linux
dist: trusty
sudo: true
language: minimal
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:13.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: "OS X - Node 8"
- 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 --env PREBUILD_TOKEN --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"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 "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 "apt-get update && apt-get install -y nodejs"
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 12"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 "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 "apt-get update && apt-get install -y nodejs"
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 13"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 "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_13.x buster 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 --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 "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 "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13) - Node.js 10"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "8"
- name: "OS X - Node 10"
os: osx
osx_image: xcode9.2
osx_image: xcode10.1
language: node_js
node_js: "10"
- name: "OS X - Node 12"
- name: "macOS (10.13) - Node.js 12"
os: osx
osx_image: xcode9.2
osx_image: xcode10.1
language: node_js
node_js: "12"
- name: "OS X - Node 13"
- name: "macOS (10.13) - Node.js 13"
os: osx
osx_image: xcode10
osx_image: xcode10.1
language: node_js
node_js: "13"
- name: "macOS (10.13) - Node.js 14"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "14"
- name: "Unit test coverage report"
os: linux
dist: bionic
language: node_js
node_js: "13"
before_install: unset PREBUILD_TOKEN
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
cache:
npm: false

View File

@@ -1,10 +1,6 @@
# sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right">
```sh
npm install sharp
```
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
@@ -20,12 +16,15 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10, 12 and 13
Most modern macOS, Windows and Linux systems running Node.js v10+
do not require any additional install or runtime dependencies.
## Examples
```sh
npm install sharp
```
```javascript
const sharp = require('sharp');
```
@@ -86,14 +85,15 @@ readableStream
```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/page/install),
[API documentation](https://sharp.pixelplumbing.com/page/api),
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and
[changelog](https://sharp.pixelplumbing.com/page/changelog).
[installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/changelog).
### Contributing
@@ -102,7 +102,7 @@ covers reporting bugs, requesting features and submitting code changes.
### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,16 +1,26 @@
os: Visual Studio 2015
os: Visual Studio 2017
version: "{build}"
build: off
platform: x64
environment:
matrix:
- nodejs_version: "8"
- nodejs_version: "10"
platform: x86
- nodejs_version: "10"
platform: x64
- nodejs_version: "12"
platform: x86
- nodejs_version: "12"
platform: x64
- nodejs_version: "13"
platform: x86
- nodejs_version: "13"
platform: x64
- nodejs_version: "14"
platform: x86
- nodejs_version: "14"
platform: x64
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
- npm install -g npm@6
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
- npm install
test_script:
- npm test

View File

@@ -11,6 +11,7 @@
],
'sources': [
'src/libvips/cplusplus/VError.cpp',
'src/libvips/cplusplus/VConnection.cpp',
'src/libvips/cplusplus/VInterpolate.cpp',
'src/libvips/cplusplus/VImage.cpp'
],
@@ -29,6 +30,9 @@
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
}
},
'msvs_disabled_warnings': [
@@ -43,7 +47,11 @@
]
}, {
'target_name': 'sharp',
'defines': [
'NAPI_VERSION=3'
],
'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp'
],
'variables': {
@@ -64,11 +72,11 @@
'src/stats.cc',
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
'src/utilities.cc'
'src/utilities.cc',
'src/sharp.cc'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
'<!@(node -p "require(\'node-addon-api\').include")',
],
'conditions': [
['use_global_libvips == "true"', {
@@ -127,7 +135,6 @@
'../vendor/lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support
'../vendor/lib/libcairo.so',
'../vendor/lib/libcroco-0.6.so',
'../vendor/lib/libexif.so',
'../vendor/lib/libexpat.so',
'../vendor/lib/libffi.so',
@@ -189,10 +196,18 @@
'-Wno-cast-function-type'
]
}],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
}
},
'msvs_disabled_warnings': [

80
docs/README.md Normal file
View File

@@ -0,0 +1,80 @@
# sharp
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern macOS, Windows and Linux systems running Node.js v10+
do not require any additional install or runtime dependencies.
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon).
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/libvips/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by a small team led by
[John Cupitt](https://github.com/jcupitt).
Only small regions of uncompressed image data
are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/async/await are supported.
### Optimal
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering is disabled by default,
which for diagrams and line art often produces the same result
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -32,22 +32,27 @@ sharp('rgb.jpg')
Returns **Sharp**
**Meta**
- **since**: 0.21.2
## extractChannel
Extract a single channel from a multi-channel image.
### Parameters
- `channel` **([Number][1] \| [String][2])** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
- `channel` **([number][1] \| [string][2])** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
### Examples
```javascript
sharp(input)
.extractChannel('green')
.toFile('input_green.jpg', function(err, info) {
.toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// info.channels === 1
// input_green.jpg contains the green channel of the input image
// green.jpg is a greyscale image containing the green channel of the input
});
```
@@ -70,7 +75,7 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
### Parameters
- `images` **([Array][4]&lt;([String][2] \| [Buffer][5])> | [String][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `images` **([Array][4]&lt;([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor.
@@ -84,7 +89,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters
- `boolOp` **[String][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples

View File

@@ -7,7 +7,7 @@ An alpha channel may be present and will be unchanged by the operation.
### Parameters
- `rgb` **([String][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
- `rgb` **([string][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
- Throws **[Error][4]** Invalid parameter
@@ -46,7 +46,7 @@ By default output image will be web-friendly sRGB, with additional channels inte
### Parameters
- `colourspace` **[String][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
- `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
- Throws **[Error][4]** Invalid parameters
@@ -59,7 +59,7 @@ Alternative spelling of `toColourspace`.
### Parameters
- `colorspace` **[String][1]?** output colorspace.
- `colorspace` **[string][1]?** output colorspace.
- Throws **[Error][4]** Invalid parameters

View File

@@ -20,7 +20,7 @@ and [https://www.cairographics.org/operators/][2]
### Parameters
- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?**
@@ -61,6 +61,10 @@ sharp('input.png')
Returns **Sharp**
**Meta**
- **since**: 0.22.0
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
[2]: https://www.cairographics.org/operators/

View File

@@ -11,6 +11,11 @@
- `options` **[Object][3]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** 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`)
- `options.limitInputPixels` **([Number][5] \| [Boolean][4])** Do not process input images where the number of pixels
(width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
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][4]** 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`)
- `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`)
- `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
@@ -67,43 +72,74 @@ sharp({
Returns **[Sharp][8]**
### format
## clone
An Object containing nested boolean values representing the available input and output formats/methods.
#### Examples
```javascript
console.log(sharp.format);
```
Returns **[Object][3]**
### versions
An Object containing the version numbers of libvips and its dependencies.
#### Examples
```javascript
console.log(sharp.versions);
```
## queue
An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread
- complete
Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
### Examples
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
const pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
```
```javascript
// Create a pipeline that will download an image, resize it and format it to different files
// Using Promises to know when the pipeline is complete
const fs = require("fs");
const got = require("got");
const sharpStream = sharp({
failOnError: false
});
const promises = [];
promises.push(
sharpStream
.clone()
.jpeg({ quality: 100 })
.toFile("originalFile.jpg")
);
promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.jpeg({ quality: 80 })
.toFile("optimized-500.jpg")
);
promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.webp({ quality: 80 })
.toFile("optimized-500.webp")
);
// https://github.com/sindresorhus/got#gotstreamurl-options
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
Promise.all(promises)
.then(res => { console.log("Done!", res); })
.catch(err => {
console.error("Error processing files, let's clean it up", err);
try {
fs.unlinkSync("originalFile.jpg");
fs.unlinkSync("optimized-500.jpg");
fs.unlinkSync("optimized-500.webp");
} catch (e) {}
});
```
Returns **[Sharp][8]**
[1]: https://nodejs.org/api/buffer.html
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

View File

@@ -1,24 +1,5 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## clone
Take a "snapshot" of the Sharp instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
### Examples
```javascript
const pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
```
Returns **Sharp**
## metadata
Fast access to (uncached) image metadata without decoding any compressed image data.
@@ -35,7 +16,9 @@ A `Promise` is returned when `callback` is not provided.
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in this PDF image will be.
- `pageHeight`: Number of pixels high each page in a multi-page image will be.
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -85,7 +68,7 @@ A `Promise` is returned when `callback` is not provided.
- `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
### Parameters
@@ -105,37 +88,9 @@ image
Returns **[Promise][5]&lt;[Object][6]>**
## limitInputPixels
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
Do not process input images where the number of pixels (width x height) exceeds this limit.
Assumes image dimensions contained in the input metadata can be trusted.
The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
### Parameters
- `limit` **([Number][7] \| [Boolean][8])** an integral Number of pixels, zero or false to remove limit, true to use default limit.
- Throws **[Error][9]** Invalid limit
Returns **Sharp**
## sequentialRead
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
This will reduce memory usage and can improve performance on some systems.
The default behaviour _before_ function call is `false`, meaning the libvips access method is not sequential.
### Parameters
- `sequentialRead` **[Boolean][8]** (optional, default `true`)
Returns **Sharp**
[1]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636
[2]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672
[2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
[3]: https://www.npmjs.com/package/icc
@@ -144,9 +99,3 @@ Returns **Sharp**
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

View File

@@ -21,9 +21,9 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
### Parameters
- `angle` **[Number][1]** angle of rotation. (optional, default `auto`)
- `angle` **[number][1]** angle of rotation. (optional, default `auto`)
- `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.background` **([string][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
### Examples
@@ -74,9 +74,9 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
### Parameters
- `sigma` **[Number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[Number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error][5]** Invalid parameters
@@ -90,7 +90,7 @@ When used without parameters the default window is 3x3.
### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`)
- `size` **[number][1]** square mask size: size x size (optional, default `3`)
- Throws **[Error][5]** Invalid parameters
@@ -105,7 +105,7 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters
- `sigma` **[Number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `sigma` **[number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- Throws **[Error][5]** Invalid parameters
@@ -119,7 +119,7 @@ Merge alpha transparency channel, if any, with a background.
### Parameters
- `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
- `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp**
@@ -135,8 +135,8 @@ Supply a second argument to use a different output gamma value, otherwise the fi
### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- `gamma` **[number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error][5]** Invalid parameters
@@ -180,11 +180,11 @@ Convolve the image with the specified kernel.
### Parameters
- `kernel` **[Object][2]**
- `kernel.width` **[Number][1]** width of the kernel in pixels.
- `kernel.height` **[Number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&lt;[Number][1]>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[Number][1]** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number][1]** the offset of the kernel in pixels. (optional, default `0`)
- `kernel.width` **[number][1]** width of the kernel in pixels.
- `kernel.height` **[number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&lt;[number][1]>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[number][1]** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[number][1]** the offset of the kernel in pixels. (optional, default `0`)
### Examples
@@ -212,7 +212,7 @@ Any pixel value greather than or equal to the threshold value will be set to 255
### Parameters
- `threshold` **[Number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `threshold` **[number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object][2]?**
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
@@ -231,13 +231,13 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters
- `operand` **([Buffer][8] \| [String][3])** Buffer containing image data or String containing the path to an image file.
- `operator` **[String][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `operand` **([Buffer][8] \| [string][3])** Buffer containing image data or string containing the path to an image file.
- `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `options` **[Object][2]?**
- `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number][1]?**
- `options.raw.height` **[Number][1]?**
- `options.raw.channels` **[Number][1]?**
- `options.raw.width` **[number][1]?**
- `options.raw.height` **[number][1]?**
- `options.raw.channels` **[number][1]?**
- Throws **[Error][5]** Invalid parameters
@@ -250,8 +250,8 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`)
- `a` **[number][1]** multiplier (optional, default `1.0`)
- `b` **[number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters
@@ -264,8 +264,7 @@ Recomb the image with the specified matrix.
### Parameters
- `inputMatrix`
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
- `inputMatrix` **[Array][7]&lt;[Array][7]&lt;[number][1]>>** 3x3 Recombination matrix
### Examples
@@ -287,6 +286,10 @@ sharp(input)
Returns **Sharp**
**Meta**
- **since**: 0.21.1
## modulate
Transforms the image using brightness, saturation and hue rotation.
@@ -294,9 +297,9 @@ Transforms the image using brightness, saturation and hue rotation.
### Parameters
- `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation
- `options.brightness` **[number][1]?** Brightness multiplier
- `options.saturation` **[number][1]?** Saturation multiplier
- `options.hue` **[number][1]?** Degrees for hue rotation
### Examples
@@ -322,6 +325,10 @@ sharp(input)
Returns **Sharp**
**Meta**
- **since**: 0.22.1
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -15,7 +15,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters
- `fileOut` **[String][2]** the path to write the image data to.
- `fileOut` **[string][2]** the path to write the image data to.
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
@@ -62,7 +62,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters
- `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][3]?**
### Examples
@@ -91,13 +91,15 @@ Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
This will also convert to and add a web-friendly sRGB ICC profile.
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
sRGB colour space and strip all metadata, including the removal of any ICC profile.
### Parameters
- `options` **[Object][6]?**
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
- `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
### Examples
@@ -112,6 +114,28 @@ sharp('input.jpg')
Returns **Sharp**
## toFormat
Force output to a given format.
### Parameters
- `format` **([string][2] \| [Object][6])** as a string or an Object with an 'id' attribute
- `options` **[Object][6]** output options
### Examples
```javascript
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
.toBuffer();
```
- Throws **[Error][4]** unsupported format or options
Returns **Sharp**
## jpeg
Use these JPEG options for output image.
@@ -119,18 +143,18 @@ Use these JPEG options for output image.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[string][2]** for quality &lt; 90, set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' (use chroma subsampling); for quality >= 90 chroma is never subsampled (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -158,15 +182,15 @@ Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
### Parameters
- `options` **[Object][6]?**
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
- `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -188,13 +212,13 @@ Use these WebP options for output image.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][7]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[Boolean][7]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[Number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -216,17 +240,17 @@ Use these TIFF options for output image.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`)
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[boolean][7]** vertical tile size (optional, default `256`)
- `options.xres` **[number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`)
### Examples
@@ -259,18 +283,24 @@ Most versions of libheif support only the patent-encumbered HEVC compression for
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`)
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- Throws **[Error][4]** Invalid options
Returns **Sharp**
**Meta**
- **since**: 0.23.0
## raw
Force output to be raw, uncompressed uint8 pixel data.
Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
Pixel ordering is left-to-right, top-to-bottom, without padding.
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
### Examples
@@ -281,28 +311,16 @@ const { data, info } = await sharp('input.jpg')
.toBuffer({ resolveWithObject: true });
```
Returns **Sharp**
## toFormat
Force output to a given format.
### Parameters
- `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute
- `options` **[Object][6]** output options
### Examples
```javascript
// Convert any input to PNG output
const data = await sharp(input)
.toFormat('png')
// Extract alpha channel as raw pixel data from PNG input
const data = await sharp('input.png')
.ensureAlpha()
.extractChannel(3)
.toColourspace('b-w')
.raw()
.toBuffer();
```
- Throws **[Error][4]** unsupported format or options
Returns **Sharp**
## tile
@@ -316,14 +334,14 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters
- `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `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.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
- `options.size` **[number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.background` **([string][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.depth` **[string][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `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.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples

View File

@@ -6,19 +6,21 @@ Resize image to `width`, `height` or `width x height`.
When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cover`: Crop to cover both provided dimensions (the default).
- `contain`: Embed within both provided dimensions.
- `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
- `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
Some of these values are based on the [object-fit][1] CSS property.
Some of these values are based on the [object-fit][1] CSS property.
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
Some of these values are based on the [object-position][2] CSS property.
Some of these values are based on the [object-position][2] CSS property.
The experimental strategy-based approach resizes so one dimension is at its target length
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
@@ -36,8 +38,8 @@ Possible interpolation kernels are:
### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
@@ -114,6 +116,15 @@ sharp(input)
});
```
```javascript
const scaleByHalf = await sharp(input)
.metadata()
.then(({ width }) => sharp(input)
.resize(Math.round(width * 0.5))
.toBuffer()
);
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**
@@ -125,11 +136,11 @@ This operation will always occur after resizing and extraction, if any.
### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][8]?**
- `extend.left` **[Number][8]?**
- `extend.bottom` **[Number][8]?**
- `extend.right` **[Number][8]?**
- `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[number][8]?**
- `extend.left` **[number][8]?**
- `extend.bottom` **[number][8]?**
- `extend.right` **[number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples
@@ -155,7 +166,7 @@ Returns **Sharp**
## extract
Extract a region of the image.
Extract/crop a region of the image.
- Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
@@ -164,10 +175,10 @@ Extract a region of the image.
### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract
- `options.left` **[number][8]** zero-indexed offset from left edge
- `options.top` **[number][8]** zero-indexed offset from top edge
- `options.width` **[number][8]** width of region to extract
- `options.height` **[number][8]** height of region to extract
### Examples
@@ -202,7 +213,7 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
### Parameters
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][13]** Invalid parameters

View File

@@ -1,5 +1,27 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## format
An Object containing nested boolean values representing the available input and output formats/methods.
### Examples
```javascript
console.log(sharp.format);
```
Returns **[Object][1]**
## versions
An Object containing the version numbers of libvips and its dependencies.
### Examples
```javascript
console.log(sharp.versions);
```
## cache
Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
@@ -9,10 +31,10 @@ useful for determining how much working memory is required for a particular task
### 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.memory` **[Number][3]** 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.items` **[Number][3]** is the maximum number of operations to cache (optional, default `100`)
- `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.memory` **[number][3]** 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.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`)
### Examples
@@ -42,7 +64,7 @@ This method always returns the current concurrency.
### Parameters
- `concurrency` **[Number][3]?**
- `concurrency` **[number][3]?**
### Examples
@@ -52,7 +74,22 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4
```
Returns **[Number][3]** concurrency
Returns **[number][3]** concurrency
## queue
An EventEmitter that emits a `change` event when a task is either:
- queued, waiting for _libuv_ to provide a worker thread
- complete
### Examples
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
## counters
@@ -79,7 +116,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
### Parameters
- `simd` **[Boolean][2]** (optional, default `true`)
- `simd` **[boolean][2]** (optional, default `true`)
### Examples
@@ -93,7 +130,7 @@ const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime
```
Returns **[Boolean][2]**
Returns **[boolean][2]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -1,10 +1,102 @@
# Changelog
### v0.23 - "*vision*"
## v0.25 - *yield*
Requires libvips v8.9.1
### v0.25.3 - 17th May 2020
* Ensure libvips is initialised only once, improves worker thread safety.
[#2143](https://github.com/lovell/sharp/issues/2143)
* Ensure npm platform flag is respected when copying DLLs.
[#2188](https://github.com/lovell/sharp/pull/2188)
[@dimadeveatii](https://github.com/dimadeveatii)
* Allow SVG input with large inline images to be parsed.
[#2195](https://github.com/lovell/sharp/issues/2195)
### v0.25.2 - 20th March 2020
* Provide prebuilt binaries for Linux ARM64v8.
* Add IIIF layout support to tile-based output.
[#2098](https://github.com/lovell/sharp/pull/2098)
[@edsilv](https://github.com/edsilv)
* Ensure input options are consistently and correctly detected.
[#2118](https://github.com/lovell/sharp/issues/2118)
* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives.
[#2119](https://github.com/lovell/sharp/issues/2119)
* Ensure AsyncWorker options are persisted.
[#2130](https://github.com/lovell/sharp/issues/2130)
### v0.25.1 - 7th March 2020
* Ensure prebuilt binaries are fetched based on N-API version.
[#2117](https://github.com/lovell/sharp/issues/2117)
### v0.25.0 - 7th March 2020
* Remove `limitInputPixels` and `sequentialRead` previously deprecated in v0.24.0.
* Migrate internals to N-API.
[#1282](https://github.com/lovell/sharp/issues/1282)
* Add support for 32-bit Windows.
[#2088](https://github.com/lovell/sharp/issues/2088)
* Ensure correct ordering of rotate-then-trim operations.
[#2087](https://github.com/lovell/sharp/issues/2087)
* Ensure composite accepts `limitInputPixels` and `sequentialRead` input options.
[#2099](https://github.com/lovell/sharp/issues/2099)
## v0.24 - "*wit*"
Requires libvips v8.9.0.
### v0.24.1 - 15<sup>th</sup> February 2020
* Prevent use of sequentialRead for EXIF-based rotate operation.
[#2042](https://github.com/lovell/sharp/issues/2042)
* Ensure RGBA LZW TIFF returns correct channel count.
[#2064](https://github.com/lovell/sharp/issues/2064)
### v0.24.0 - 16<sup>th</sup> January 2020
* Drop support for Node.js 8.
[#1910](https://github.com/lovell/sharp/issues/1910)
* Drop support for undefined input where options also provided.
[#1768](https://github.com/lovell/sharp/issues/1768)
* Move `limitInputPixels` and `sequentialRead` to input options, deprecating functions of the same name.
* Expose `delay` and `loop` metadata for animated images.
[#1905](https://github.com/lovell/sharp/issues/1905)
* Ensure correct colour output for 16-bit, 2-channel PNG input with ICC profile.
[#2013](https://github.com/lovell/sharp/issues/2013)
* Prevent use of sequentialRead for rotate operations.
[#2016](https://github.com/lovell/sharp/issues/2016)
* Correctly bind max width and height values when using withoutEnlargement.
[#2024](https://github.com/lovell/sharp/pull/2024)
[@BrychanOdlum](https://github.com/BrychanOdlum)
* Add support for input with 16-bit RGB profile.
[#2037](https://github.com/lovell/sharp/issues/2037)
## v0.23 - "*vision*"
Requires libvips v8.8.1.
#### v0.23.4 - 5<sup>th</sup> December 2019
### v0.23.4 - 5<sup>th</sup> December 2019
* Handle zero-length Buffer objects when using Node.js v13.2.0+.
@@ -14,7 +106,7 @@ Requires libvips v8.8.1.
* Improve thread safety by using copy-on-write when updating metadata.
[#1986](https://github.com/lovell/sharp/issues/1986)
#### v0.23.3 - 17<sup>th</sup> November 2019
### v0.23.3 - 17<sup>th</sup> November 2019
* Ensure `trim` operation supports images contained in the alpha channel.
[#1597](https://github.com/lovell/sharp/issues/1597)
@@ -30,7 +122,7 @@ Requires libvips v8.8.1.
* Ensure `modulate` and other colour-based operations can co-exist.
[#1958](https://github.com/lovell/sharp/issues/1958)
#### v0.23.2 - 28<sup>th</sup> October 2019
### v0.23.2 - 28<sup>th</sup> October 2019
* Add `background` option to tile output operation.
[#1924](https://github.com/lovell/sharp/pull/1924)
@@ -40,7 +132,7 @@ Requires libvips v8.8.1.
[#1932](https://github.com/lovell/sharp/pull/1932)
[@MayhemYDG](https://github.com/MayhemYDG)
#### v0.23.1 - 26<sup>th</sup> September 2019
### v0.23.1 - 26<sup>th</sup> September 2019
* Ensure `sharp.format.vips` is present and correct (filesystem only).
[#1813](https://github.com/lovell/sharp/issues/1813)
@@ -62,7 +154,7 @@ Requires libvips v8.8.1.
* Ensure image is at least 3x3 pixels before attempting trim operation.
#### v0.23.0 - 29<sup>th</sup> July 2019
### v0.23.0 - 29<sup>th</sup> July 2019
* Remove `overlayWith` previously deprecated in v0.22.0.
@@ -92,11 +184,11 @@ Requires libvips v8.8.1.
[#1755](https://github.com/lovell/sharp/pull/1755)
[@iovdin](https://github.com/iovdin)
### v0.22 - "*uptake*"
## v0.22 - "*uptake*"
Requires libvips v8.7.4.
#### v0.22.1 - 25<sup>th</sup> April 2019
### v0.22.1 - 25<sup>th</sup> April 2019
* Add `modulate` operation for brightness, saturation and hue.
[#1601](https://github.com/lovell/sharp/pull/1601)
@@ -109,7 +201,7 @@ Requires libvips v8.7.4.
* Add support for Node 12.
[#1668](https://github.com/lovell/sharp/issues/1668)
#### v0.22.0 - 18<sup>th</sup> March 2019
### v0.22.0 - 18<sup>th</sup> March 2019
* Remove functions previously deprecated in v0.21.0:
`background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
@@ -127,18 +219,18 @@ Requires libvips v8.7.4.
[#1595](https://github.com/lovell/sharp/pull/1595)
[@ramiel](https://github.com/ramiel)
### v0.21 - "*teeth*"
## v0.21 - "*teeth*"
Requires libvips v8.7.0.
#### v0.21.3 - 19<sup>th</sup> January 2019
### v0.21.3 - 19<sup>th</sup> January 2019
* Input image decoding now fails fast, set `failOnError` to change this behaviour.
* Failed filesystem-based input now separates missing file and invalid format errors.
[#1542](https://github.com/lovell/sharp/issues/1542)
#### v0.21.2 - 13<sup>th</sup> January 2019
### v0.21.2 - 13<sup>th</sup> January 2019
* Ensure all metadata is removed from PNG output unless `withMetadata` used.
@@ -163,7 +255,7 @@ Requires libvips v8.7.0.
* Ensure forced output format applied correctly when output chaining.
[#1528](https://github.com/lovell/sharp/issues/1528)
#### v0.21.1 - 7<sup>th</sup> December 2018
### v0.21.1 - 7<sup>th</sup> December 2018
* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`.
[#1422](https://github.com/lovell/sharp/pull/1422)
@@ -192,7 +284,7 @@ Requires libvips v8.7.0.
[#1483](https://github.com/lovell/sharp/pull/1483)
[@mbklein](https://github.com/mbklein)
#### v0.21.0 - 4<sup>th</sup> October 2018
### v0.21.0 - 4<sup>th</sup> October 2018
* Deprecate the following resize-related functions:
`crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
@@ -229,11 +321,11 @@ Requires libvips v8.7.0.
[#1385](https://github.com/lovell/sharp/pull/1385)
[@freezy](https://github.com/freezy)
### v0.20 - "*prebuild*"
## v0.20 - "*prebuild*"
Requires libvips v8.6.1.
#### v0.20.8 - 5<sup>th</sup> September 2018
### v0.20.8 - 5<sup>th</sup> September 2018
* Avoid race conditions when creating directories during installation.
[#1358](https://github.com/lovell/sharp/pull/1358)
@@ -243,12 +335,12 @@ Requires libvips v8.6.1.
[#1362](https://github.com/lovell/sharp/pull/1362)
[@aeirola](https://github.com/aeirola)
#### v0.20.7 - 21<sup>st</sup> August 2018
### v0.20.7 - 21<sup>st</sup> August 2018
* Use copy+unlink if rename operation fails during installation.
[#1345](https://github.com/lovell/sharp/issues/1345)
#### v0.20.6 - 20<sup>th</sup> August 2018
### v0.20.6 - 20<sup>th</sup> August 2018
* Add removeAlpha operation to remove alpha channel, if any.
[#1248](https://github.com/lovell/sharp/issues/1248)
@@ -279,13 +371,13 @@ Requires libvips v8.6.1.
* Add experimental entropy field to stats response.
#### v0.20.5 - 27<sup>th</sup> June 2018
### v0.20.5 - 27<sup>th</sup> June 2018
* Expose libjpeg optimize_coding flag.
[#1265](https://github.com/lovell/sharp/pull/1265)
[@tomlokhorst](https://github.com/tomlokhorst)
#### v0.20.4 - 20<sup>th</sup> June 2018
### v0.20.4 - 20<sup>th</sup> June 2018
* Prevent possible rounding error when using shrink-on-load and 90/270 degree rotation.
[#1241](https://github.com/lovell/sharp/issues/1241)
@@ -295,13 +387,13 @@ Requires libvips v8.6.1.
[#1257](https://github.com/lovell/sharp/issues/1257)
[@jeremychone](https://github.com/jeremychone)
#### v0.20.3 - 29<sup>th</sup> May 2018
### v0.20.3 - 29<sup>th</sup> May 2018
* Fix tint operation by ensuring LAB interpretation and allowing negative values.
[#1235](https://github.com/lovell/sharp/issues/1235)
[@wezside](https://github.com/wezside)
#### v0.20.2 - 28<sup>th</sup> April 2018
### v0.20.2 - 28<sup>th</sup> April 2018
* Add tint operation to set image chroma.
[#825](https://github.com/lovell/sharp/pull/825)
@@ -319,7 +411,7 @@ Requires libvips v8.6.1.
[#1208](https://github.com/lovell/sharp/pull/1208)
[@woolite64](https://github.com/woolite64)
#### v0.20.1 - 17<sup>th</sup> March 2018
### v0.20.1 - 17<sup>th</sup> March 2018
* Improve installation experience when a globally-installed libvips below the minimum required version is found.
[#1148](https://github.com/lovell/sharp/issues/1148)
@@ -332,16 +424,16 @@ Requires libvips v8.6.1.
[#1161](https://github.com/lovell/sharp/pull/1161)
[@BiancoA](https://github.com/BiancoA)
#### v0.20.0 - 5<sup>th</sup> March 2018
### v0.20.0 - 5<sup>th</sup> March 2018
* Add support for prebuilt sharp binaries on common platforms.
[#186](https://github.com/lovell/sharp/issues/186)
### v0.19 - "*suit*"
## v0.19 - "*suit*"
Requires libvips v8.6.1.
#### v0.19.1 - 24<sup>th</sup> February 2018
### v0.19.1 - 24<sup>th</sup> February 2018
* Expose libvips' linear transform feature.
[#1024](https://github.com/lovell/sharp/pull/1024)
@@ -355,7 +447,7 @@ Requires libvips v8.6.1.
[#1134](https://github.com/lovell/sharp/issues/1134)
[@pieh](https://github.com/pieh)
#### v0.19.0 - 11<sup>th</sup> January 2018
### v0.19.0 - 11<sup>th</sup> January 2018
* Expose offset coordinates of strategy-based crop.
[#868](https://github.com/lovell/sharp/issues/868)
@@ -399,17 +491,17 @@ Requires libvips v8.6.1.
* TIFF output: switch default predictor from 'none' to 'horizontal' to match libvips' behaviour.
### v0.18 - "*ridge*"
## v0.18 - "*ridge*"
Requires libvips v8.5.5.
#### v0.18.4 - 18<sup>th</sup> September 2017
### v0.18.4 - 18<sup>th</sup> September 2017
* Ensure input Buffer really is marked as Persistent, prevents mark-sweep GC.
[#950](https://github.com/lovell/sharp/issues/950)
[@lfdoherty](https://github.com/lfdoherty)
#### v0.18.3 - 13<sup>th</sup> September 2017
### v0.18.3 - 13<sup>th</sup> September 2017
* Skip shrink-on-load when trimming.
[#888](https://github.com/lovell/sharp/pull/888)
@@ -419,7 +511,7 @@ Requires libvips v8.5.5.
[#945](https://github.com/lovell/sharp/pull/945)
[@pbomb](https://github.com/pbomb)
#### v0.18.2 - 1<sup>st</sup> July 2017
### v0.18.2 - 1<sup>st</sup> July 2017
* Expose libvips' xres and yres properties for TIFF output.
[#828](https://github.com/lovell/sharp/pull/828)
@@ -436,13 +528,13 @@ Requires libvips v8.5.5.
[#857](https://github.com/lovell/sharp/pull/857)
[@ekremkaraca](https://github.com/ekremkaraca)
#### v0.18.1 - 30<sup>th</sup> May 2017
### v0.18.1 - 30<sup>th</sup> May 2017
* Remove regression from #781 that could cause incorrect shrink calculation.
[#831](https://github.com/lovell/sharp/issues/831)
[@suprMax](https://github.com/suprMax)
#### v0.18.0 - 30<sup>th</sup> May 2017
### v0.18.0 - 30<sup>th</sup> May 2017
* Remove the previously-deprecated output format "option" functions:
quality, progressive, compressionLevel, withoutAdaptiveFiltering,
@@ -500,11 +592,11 @@ Requires libvips v8.5.5.
[#814](https://github.com/lovell/sharp/pull/814)
[@jingsam](https://github.com/jingsam)
### v0.17 - "*quill*"
## v0.17 - "*quill*"
Requires libvips v8.4.2.
#### v0.17.3 - 1<sup>st</sup> April 2017
### v0.17.3 - 1<sup>st</sup> April 2017
* Allow toBuffer to optionally resolve a Promise with both info and data.
[#143](https://github.com/lovell/sharp/issues/143)
@@ -522,7 +614,7 @@ Requires libvips v8.4.2.
[#738](https://github.com/lovell/sharp/pull/738)
[@kristojorg](https://github.com/kristojorg)
#### v0.17.2 - 11<sup>th</sup> February 2017
### v0.17.2 - 11<sup>th</sup> February 2017
* Ensure Readable side of Stream can start flowing after Writable side has finished.
[#671](https://github.com/lovell/sharp/issues/671)
@@ -532,7 +624,7 @@ Requires libvips v8.4.2.
[#685](https://github.com/lovell/sharp/pull/685)
[@rnanwani](https://github.com/rnanwani)
#### v0.17.1 - 15<sup>th</sup> January 2017
### v0.17.1 - 15<sup>th</sup> January 2017
* Improve error messages for invalid parameters.
[@spikeon](https://github.com/spikeon)
@@ -545,7 +637,7 @@ Requires libvips v8.4.2.
[@wangzhiwei1888](https://github.com/wangzhiwei1888)
[#679](https://github.com/lovell/sharp/issues/679)
#### v0.17.0 - 11<sup>th</sup> December 2016
### v0.17.0 - 11<sup>th</sup> December 2016
* Drop support for versions of Node prior to v4.
@@ -582,17 +674,17 @@ Requires libvips v8.4.2.
[#646](https://github.com/lovell/sharp/issues/646)
[@DaGaMs](https://github.com/DaGaMs)
### v0.16 - "*pencil*"
## v0.16 - "*pencil*"
Requires libvips v8.3.3
#### v0.16.2 - 22<sup>nd</sup> October 2016
### v0.16.2 - 22<sup>nd</sup> October 2016
* Restrict readelf usage to Linux only when detecting global libvips version.
[#602](https://github.com/lovell/sharp/issues/602)
[@caoko](https://github.com/caoko)
#### v0.16.1 - 13<sup>th</sup> October 2016
### v0.16.1 - 13<sup>th</sup> October 2016
* C++11 ABI version is now auto-detected, remove sharp-cxx11 installation flag.
@@ -611,7 +703,7 @@ Requires libvips v8.3.3
[#566](https://github.com/lovell/sharp/issues/566)
[@Nateowami](https://github.com/Nateowami)
#### v0.16.0 - 18<sup>th</sup> August 2016
### v0.16.0 - 18<sup>th</sup> August 2016
* Add pre-compiled libvips for OS X, ARMv7 and ARMv8.
[#312](https://github.com/lovell/sharp/issues/312)
@@ -651,11 +743,11 @@ Requires libvips v8.3.3
* Remove deprecated interpolateWith method - use resize(w, h, { interpolator: ... })
[#310](https://github.com/lovell/sharp/issues/310)
### v0.15 - "*outfit*"
## v0.15 - "*outfit*"
Requires libvips v8.3.1
#### v0.15.1 - 12<sup>th</sup> July 2016
### v0.15.1 - 12<sup>th</sup> July 2016
* Concat Stream-based input in single operation for ~+3% perf and less GC.
[#429](https://github.com/lovell/sharp/issues/429)
@@ -721,7 +813,7 @@ Requires libvips v8.3.1
[#501](https://github.com/lovell/sharp/pull/501)
[@mhirsch](https://github.com/mhirsch)
#### v0.15.0 - 21<sup>st</sup> May 2016
### v0.15.0 - 21<sup>st</sup> May 2016
* Use libvips' new Lanczos 3 kernel as default for image reduction.
Deprecate interpolateWith method, now provided as a resize option.
@@ -739,11 +831,11 @@ Requires libvips v8.3.1
[#413](https://github.com/lovell/sharp/issues/413)
[@jardakotesovec](https://github.com/jardakotesovec)
### v0.14 - "*needle*"
## v0.14 - "*needle*"
Requires libvips v8.2.3
#### v0.14.1 - 16<sup>th</sup> April 2016
### v0.14.1 - 16<sup>th</sup> April 2016
* Allow removal of limitation on input pixel count via limitInputPixels. Use with care.
[#250](https://github.com/lovell/sharp/issues/250)
@@ -767,7 +859,7 @@ Requires libvips v8.2.3
[#412](https://github.com/lovell/sharp/issues/412)
[@nouh](https://github.com/nouh)
#### v0.14.0 - 2<sup>nd</sup> April 2016
### v0.14.0 - 2<sup>nd</sup> April 2016
* Add ability to extend (pad) the edges of an image.
[#128](https://github.com/lovell/sharp/issues/128)
@@ -804,17 +896,17 @@ Requires libvips v8.2.3
* Remove deprecated style of calling extract API. Breaks calls using positional arguments.
[#276](https://github.com/lovell/sharp/issues/276)
### v0.13 - "*mind*"
## v0.13 - "*mind*"
Requires libvips v8.2.2
#### v0.13.1 - 27<sup>th</sup> February 2016
### v0.13.1 - 27<sup>th</sup> February 2016
* Fix embedding onto transparent backgrounds; regression introduced in v0.13.0.
[#366](https://github.com/lovell/sharp/issues/366)
[@diegocsandrim](https://github.com/diegocsandrim)
#### v0.13.0 - 15<sup>th</sup> February 2016
### v0.13.0 - 15<sup>th</sup> February 2016
* Improve vector image support by allowing control of density/DPI.
Switch pre-built libs from Imagemagick to Graphicsmagick.
@@ -859,11 +951,11 @@ Requires libvips v8.2.2
* Add support for gamma correction of images with an alpha channel.
### v0.12 - "*look*"
## v0.12 - "*look*"
Requires libvips v8.2.0
#### v0.12.2 - 16<sup>th</sup> January 2016
### v0.12.2 - 16<sup>th</sup> January 2016
* Upgrade libvips to v8.2.0 for improved vips_shrink.
@@ -881,7 +973,7 @@ Requires libvips v8.2.0
[#331](https://github.com/lovell/sharp/issues/331)
[@dtoubelis](https://github.com/dtoubelis)
#### v0.12.1 - 12<sup>th</sup> December 2015
### v0.12.1 - 12<sup>th</sup> December 2015
* Allow use of SIMD vector instructions (via liborc) to be toggled on/off.
[#172](https://github.com/lovell/sharp/issues/172)
@@ -894,7 +986,7 @@ Requires libvips v8.2.0
* Use the NPM-configured HTTPS proxy, if any, for binary downloads.
#### v0.12.0 - 23<sup>rd</sup> November 2015
### v0.12.0 - 23<sup>rd</sup> November 2015
* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows.
[#42](https://github.com/lovell/sharp/issues/42)
@@ -930,9 +1022,9 @@ Requires libvips v8.2.0
[#309](https://github.com/lovell/sharp/pull/309)
[@papandreou](https://github.com/papandreou)
### v0.11 - "*knife*"
## v0.11 - "*knife*"
#### v0.11.4 - 5<sup>th</sup> November 2015
### v0.11.4 - 5<sup>th</sup> November 2015
* Add corners, e.g. `northeast`, to existing `gravity` option.
[#291](https://github.com/lovell/sharp/pull/291)
@@ -946,13 +1038,13 @@ Requires libvips v8.2.0
[#287](https://github.com/lovell/sharp/pull/287)
[@vlapo](https://github.com/vlapo)
#### v0.11.3 - 8<sup>th</sup> September 2015
### v0.11.3 - 8<sup>th</sup> September 2015
* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision.
[#263](https://github.com/lovell/sharp/pull/263)
[@chrisriley](https://github.com/chrisriley)
#### v0.11.2 - 28<sup>th</sup> August 2015
### v0.11.2 - 28<sup>th</sup> August 2015
* Allow crop gravity to be provided as a String.
[#255](https://github.com/lovell/sharp/pull/255)
@@ -960,7 +1052,7 @@ Requires libvips v8.2.0
* Add support for io.js v3 and Node v4.
[#246](https://github.com/lovell/sharp/issues/246)
#### v0.11.1 - 12<sup>th</sup> August 2015
### v0.11.1 - 12<sup>th</sup> August 2015
* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled".
[#244](https://github.com/lovell/sharp/pull/244)
@@ -970,7 +1062,7 @@ Requires libvips v8.2.0
[#249](https://github.com/lovell/sharp/issues/249)
[@compeak](https://github.com/compeak)
#### v0.11.0 - 15<sup>th</sup> July 2015
### v0.11.0 - 15<sup>th</sup> July 2015
* Allow alpha transparency compositing via new `overlayWith` method.
[#97](https://github.com/lovell/sharp/issues/97)
@@ -998,9 +1090,9 @@ Requires libvips v8.2.0
[#238](https://github.com/lovell/sharp/issues/238)
[@richardadjogah](https://github.com/richardadjogah)
### v0.10 - "*judgment*"
## v0.10 - "*judgment*"
#### v0.10.1 - 1<sup>st</sup> June 2015
### v0.10.1 - 1<sup>st</sup> June 2015
* Allow embed of image with alpha transparency onto non-transparent background.
[#204](https://github.com/lovell/sharp/issues/204)
@@ -1010,7 +1102,7 @@ Requires libvips v8.2.0
[#228](https://github.com/lovell/sharp/issues/228)
[@doggan](https://github.com/doggan)
#### v0.10.0 - 23<sup>rd</sup> April 2015
### v0.10.0 - 23<sup>rd</sup> April 2015
* Add support for Windows (x86).
[#19](https://github.com/lovell/sharp/issues/19)

View File

@@ -1,5 +0,0 @@
/* Nest document subheadings in navigation */
ul.subnav ul:not(.subnav) {
padding-left: 2em;
font-size: 80%;
}

117
docs/firebase.json Normal file
View File

@@ -0,0 +1,117 @@
{
"hosting": {
"site": "pixelplumbing-sharp",
"public": ".",
"ignore": [
".*",
"firebase.json",
"*.md",
"image/**",
"search-index/**"
],
"headers": [
{
"source": "**",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=86400"
}
]
}
],
"redirects": [
{
"source": "**/install/**",
"destination": "/install",
"type": 301
},
{
"source": "/page/install",
"destination": "/install",
"type": 301
},
{
"source": "**/api-constructor/**",
"destination": "/api-constructor",
"type": 301
},
{
"source": "**/api-input/**",
"destination": "/api-input",
"type": 301
},
{
"source": "**/api-output/**",
"destination": "/api-output",
"type": 301
},
{
"source": "**/api-resize/**",
"destination": "/api-resize",
"type": 301
},
{
"source": "**/api-compsite/**",
"destination": "/api-compsite",
"type": 301
},
{
"source": "**/api-operation/**",
"destination": "/api-operation",
"type": 301
},
{
"source": "**/api-colour/**",
"destination": "/api-colour",
"type": 301
},
{
"source": "**/api-channel/**",
"destination": "/api-channel",
"type": 301
},
{
"source": "**/api-utility/**",
"destination": "/api-utility",
"type": 301
},
{
"source": "/page/api",
"destination": "/api-constructor",
"type": 301
},
{
"source": "**/performance/**",
"destination": "/performance",
"type": 301
},
{
"source": "/page/performance",
"destination": "/performance",
"type": 301
},
{
"source": "**/changelog/**",
"destination": "/changelog",
"type": 301
},
{
"source": "/page/changelog",
"destination": "/changelog",
"type": 301
},
{
"source": "/en/**",
"destination": "/",
"type": 301
}
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

184
docs/humans.txt Normal file
View File

@@ -0,0 +1,184 @@
/* THANKS */
Name: John Cupitt
GitHub: https://github.com/jcupitt
Name: Pierre Inglebert
GitHub: https://github.com/pierreinglebert
Name: Jonathan Ong
GitHub: https://github.com/jonathanong
Name: Chanon Sajjamanochai
GitHub: https://github.com/chanon
Name: Juliano Julio
GitHub: https://github.com/julianojulio
Name: Daniel Gasienica
GitHub: https://github.com/gasi
Name: Julian Walker
GitHub: https://github.com/julianwa
Name: Amit Pitaru
GitHub: https://github.com/apitaru
Name: Brandon Aaron
GitHub: https://github.com/brandonaaron
Name: Andreas Lind
GitHub: https://github.com/papandreouGitHub:
Name: Maurus Cuelenaere
GitHub: https://github.com/mcuelenaere
Name: Linus Unnebäck
GitHub: https://github.com/LinusU
Name: Victor Mateevitsi
GitHub: https://github.com/mvictoras
Name: Alaric Holloway
GitHub: https://github.com/skedastik
Name: Bernhard K. Weisshuhn
GitHub: https://github.com/bkw
Name: David A. Carley
GitHub: https://github.com/dacarley
Name: John Tobin
GitHub: https://github.com/jtobinisaniceguy
Name: Kenton Gray
GitHub: https://github.com/kentongray
Name: Felix Bünemann
GitHub: https://github.com/felixbuenemann
Name: Samy Al Zahrani
GitHub: https://github.com/salzhrani
Name: Chintan Thakkar
GitHub: https://github.com/lemnisk8
Name: F. Orlando Galashan
GitHub: https://github.com/frulo
Name: Kleis Auke Wolthuizen
GitHub: https://github.com/kleisauke
Name: Matt Hirsch
GitHub: https://github.com/mhirsch
Name: Rahul Nanwani
GitHub: https://github.com/rnanwani
Name: Matthias Thoemmes
GitHub: https://github.com/cmtt
Name: Patrick Paskaris
GitHub: https://github.com/ppaskaris
Name: Jérémy Lal
GitHub: https://github.com/kapouer
Name: Alice Monday
GitHub: https://github.com/alice0meta
Name: Kristo Jorgenson
GitHub: https://github.com/kristojorg
Name: Yves Bos
GitHub: https://github.com/YvesBos
Name: Nicolas Coden
GitHub: https://github.com/ncoden
Name: Matt Parrish
GitHub: https://github.com/pbomb
Name: Matthew McEachen
GitHub: https://github.com/mceachen
Name: Jarda Kotěšovec
GitHub: https://github.com/jardakotesovec
Name: Kenric D'Souza
GitHub: https://github.com/AzureByte
Name: Oleh Aleinyk
GitHub: https://github.com/oaleynik
Name: Marcel Bretschneider
GitHub: https://github.com/3epnm
Name: Andrea Bianco
GitHub: https://github.com/BiancoA
Name: Rik Heywood
GitHub: https://github.com/rikh42
Name: Thomas Parisot
GitHub: https://github.com/oncletom
Name: Nathan Graves
GitHub: https://github.com/woolite64
Name: Tom Lokhorst
GitHub: https://github.com/tomlokhorst
Name: Espen Hovlandsdal
GitHub: https://github.com/rexxars
Name: Sylvain Dumont
GitHub: https://github.com/sylvaindumont
Name: Alun Davies
GitHub: https://github.com/alundavies
Name: Aidan Hoolachan
GitHub: https://github.com/ajhool
Name: Axel Eirola
GitHub: https://github.com/aeirola
Name: Freezy
GitHub: https://github.com/freezy
Name: Julian Aubourg
GitHub: https://github.com/jaubourg
Name: Keith Belovay
GitHub: https://github.com/fromkeith
Name: Michael B. Klein
GitHub: https://github.com/mbklein
Name: Jakub Michálek
GitHub: https://github.com/Goues
Name: Ilya Ovdin
GitHub: https://github.com/iovdin
Name: Andargor
GitHub: https://github.com/Andargor
Name: Nicolas Stepien
GitHub: https://github.com/MayhemYDG
Name: Paul Neave
GitHub: https://github.com/neave
Name: Brendan Kennedy
GitHub: https://github.com/rustyguts
Name: Brychan Bennett-Odlum
GitHub: https://github.com/BrychanOdlum
Name: Edward Silverton
GitHub: https://github.com/edsilv
Name: Dumitru Deveatii
GitHub: https://github.com/dimadeveatii

208
docs/index.html Normal file
View File

@@ -0,0 +1,208 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<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">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://pixel.plumbing/px/120x120/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://pixel.plumbing/px/114x114/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="stylesheet" href="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.css">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.3/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="dns-prefetch" href="https://www.google-analytics.com">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareSourceCode",
"name": "sharp",
"description": "High performance Node.js image processing",
"url": "https://sharp.pixelplumbing.com",
"codeRepository": "https://github.com/lovell/sharp",
"programmingLanguage": ["JavaScript", "C++"],
"runtimePlatform": "Node.js",
"copyrightHolder": {
"@context": "https://schema.org",
"@type": "Person",
"name": "Lovell Fuller"
},
"copyrightYear": [2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020],
"license": "https://www.apache.org/licenses/LICENSE-2.0"
}
</script>
<style>
:root {
--link-color: #077;
}
@media (max-width: 576px) {
.shorten-strapline {
display: none;
}
}
img[alt='sharp logo'] {
margin: 0 0 16px 16px;
}
.page-content > h2,
.page-content.has-page-title > h2:first-child {
margin-top: 3rem;
}
.page-content h3 {
font-size: 1.4rem;
}
ul ul {
padding-left: 20px;
}
</style>
<title>sharp - High performance Node.js image processing</title>
</head>
<body>
<div id="docute"></div>
<script src="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docute-google-analytics@1/dist/index.min.js"></script>
<script>
const docuteApiTitlePlugin = {
name: 'apiTitle',
extend(api) {
api.processMarkdown(function (md) {
const title = api.store.getters.sidebarLinks
.filter(function (sidebarLink) {
return sidebarLink.link === api.router.history.current.path
})
.map(function (sidebarLink) {
return sidebarLink.title;
})[0];
return title
? md.replace(/<!-- Generated by documentation.js. Update this documentation by updating the source code. -->/, '# ' + title)
: md;
});
}
};
const docuteApiSearchPlugin = {
name: 'apiSearch',
extend(api) {
api.enableSearch({
handler: function (keyword) {
if (keyword.trim().length > 2) {
return fetch('/search-index.json')
.then(function (res) {
return res.json();
})
.then(function (entries) {
const results = [];
entries.forEach(function (entry) {
if (entry.k.includes(keyword)) {
results.push({
title: entry.t,
link: entry.l,
description: entry.d,
label: entry.l.startsWith("/api") ? "API" : "Install"
});
}
})
return results.slice(0, 3);
})
} else {
return [];
}
}
});
}
};
new Docute({
router: { mode: 'history' },
logo: '<div style="display:flex;align-items:center">'
+ '<strong>sharp</strong> '
+ '<img src="https://pixel.plumbing/px/16x16/sharp-logo.svg" style="padding:8px" alt="#"> '
+ '<span style="opacity:0.8;white-space:pre" class="shorten-strapline">High performance </span> '
+ '<span style="opacity:0.8">Node.js image processing</span> '
+ '</div>',
darkThemeToggler: true,
detectSystemDarkTheme: true,
footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>',
plugins: [
docuteGoogleAnalytics('UA-13034748-12'),
docuteApiTitlePlugin,
docuteApiSearchPlugin
],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.3/docs',
nav: [
{
title: 'Funding',
link: 'https://opencollective.com/libvips'
},
{
title: 'GitHub',
link: 'https://github.com/lovell/sharp'
}
],
sidebar: [
{
title: 'Home',
link: '/'
},
{
title: 'Installation',
link: '/install'
},
{
title: 'API',
children: [
{
title: 'Constructor',
link: '/api-constructor'
},
{
title: 'Input metadata',
link: '/api-input'
},
{
title: 'Output options',
link: '/api-output'
},
{
title: 'Resizing images',
link: '/api-resize'
},
{
title: 'Compositing images',
link: '/api-composite'
},
{
title: 'Image operations',
link: '/api-operation'
},
{
title: 'Colour manipulation',
link: '/api-colour'
},
{
title: 'Channel manipulation',
link: '/api-channel'
},
{
title: 'Global properties',
link: '/api-utility'
},
],
},
{
title: 'Performance',
link: '/performance'
},
{
title: 'Changelog',
link: '/changelog'
}
]
});
</script>
</body>
</html>

View File

@@ -1,150 +0,0 @@
# sharp
<img src="image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10, 12 and 13
do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon)
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/libvips/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by
[John Cupitt](https://github.com/jcupitt).
Only small regions of uncompressed image data
are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/async/await are supported.
### Optimal
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering is disabled by default,
which for diagrams and line art often produces the same result
as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Credits
This module would never have been possible without
the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unnebäck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
* [Chintan Thakkar](https://github.com/lemnisk8)
* [F. Orlando Galashan](https://github.com/frulo)
* [Kleis Auke Wolthuizen](https://github.com/kleisauke)
* [Matt Hirsch](https://github.com/mhirsch)
* [Rahul Nanwani](https://github.com/rnanwani)
* [Matthias Thoemmes](https://github.com/cmtt)
* [Patrick Paskaris](https://github.com/ppaskaris)
* [Jérémy Lal](https://github.com/kapouer)
* [Alice Monday](https://github.com/alice0meta)
* [Kristo Jorgenson](https://github.com/kristojorg)
* [Yves Bos](https://github.com/YvesBos)
* [Nicolas Coden](https://github.com/ncoden)
* [Matt Parrish](https://github.com/pbomb)
* [Matthew McEachen](https://github.com/mceachen)
* [Jarda Kotěšovec](https://github.com/jardakotesovec)
* [Kenric D'Souza](https://github.com/AzureByte)
* [Oleh Aleinyk](https://github.com/oaleynik)
* [Marcel Bretschneider](https://github.com/3epnm)
* [Andrea Bianco](https://github.com/BiancoA)
* [Rik Heywood](https://github.com/rikh42)
* [Thomas Parisot](https://github.com/oncletom)
* [Nathan Graves](https://github.com/woolite64)
* [Tom Lokhorst](https://github.com/tomlokhorst)
* [Espen Hovlandsdal](https://github.com/rexxars)
* [Sylvain Dumont](https://github.com/sylvaindumont)
* [Alun Davies](https://github.com/alundavies)
* [Aidan Hoolachan](https://github.com/ajhool)
* [Axel Eirola](https://github.com/aeirola)
* [Freezy](https://github.com/freezy)
* [Julian Aubourg](https://github.com/jaubourg)
* [Keith Belovay](https://github.com/fromkeith)
* [Michael B. Klein](https://github.com/mbklein)
* [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor)
* [Nicolas Stepien](https://github.com/MayhemYDG)
* [Paul Neave](https://github.com/neave)
* [Brendan Kennedy](https://github.com/rustyguts)
Thank you!
### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -10,107 +10,99 @@ yarn add sharp
## Prerequisites
* Node.js v8.5.0+
* Node.js v10+
### Building from source
## Prebuilt binaries
Pre-compiled binaries for sharp are provided for use with
Node versions 8, 10, 12 and 13 on
64-bit Windows, OS X and Linux platforms.
Ready-compiled sharp and libvips binaries are provided for use with
Node.js v10+ on the most common platforms:
Sharp will be built from source at install time when:
* macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29)
* Windows
* a globally-installed libvips is detected,
* pre-compiled binaries do not exist for the current platform and Node version, or
A ~10MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the
JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
The following platforms require compilation of both libvips and sharp from source:
* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl)
* Linux PowerPC
* FreeBSD
* OpenBSD
## Common problems
The architecture and platform of Node.js used for `npm install`
must be the same as the architecture and platform of Node.js used at runtime.
The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
Check the output of running `npm install --verbose sharp` for useful error messages.
## Custom libvips
To use a custom, globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.
For help compiling libvips from source, please see
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball).
The use of a globally-installed libvips is unsupported on Windows.
## Building from source
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),
* prebuilt binaries do not exist for the current platform and Node.js version, or
* when the `npm install --build-from-source` flag is used.
Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7)
* C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
## libvips
## Custom prebuilt binaries
### Linux
This is an advanced approach that most people will not require.
[![Ubuntu 16.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
To install the prebuilt libvips binaries from a custom URL,
set the `sharp_dist_base_url` npm config option
or the `SHARP_DIST_BASE_URL` environment variable.
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 10MB.
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 8+
* Ubuntu 14.04+
* Red Hat Enterprise 7+
* CentOS 7+
* Alpine 3.10+
* Fedora 21+
* openSUSE 13.2+
* Archlinux
* Raspbian Jessie
* Amazon Linux
* Solus
To use a globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.
If you are using non-standard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
This allows the use of newer versions of libvips with older versions of sharp.
For 32-bit Intel CPUs and older Linux-based operating systems such as
those based on Red Hat Enterprise 6 (e.g. CentOS 6)
compiling libvips from source is recommended.
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball)
#### Alpine Linux
libvips is available in the
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
For example, both of the following will result in an attempt to download the file located at
`https://hostname/path/libvips-x.y.z-platform.tar.gz`.
```sh
apk add --upgrade --no-cache vips-dev build-base \
--repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/
npm config set sharp_dist_base_url "https://hostname/path/"
npm install sharp
```
The smaller stack size of musl libc means
libvips may need to be used without a cache
via `sharp.cache(false)` to avoid a stack overflow.
```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
### Mac OS
To install the prebuilt sharp binaries from a custom URL, please see
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
[![OS X 10.12 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
## FreeBSD
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 8MB.
To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file and
that it can be located using `pkg-config --modversion vips-cpp`.
### Windows x64
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 10MB.
If you are having issues during installation consider removing the directory
`C:\Users\[user]\AppData\Roaming\npm-cache\_libvips`.
Only 64-bit (x64) `node.exe` is supported.
### FreeBSD
[![FreeBSD Build Status](https://api.cirrus-ci.com/github/lovell/sharp.svg)](https://cirrus-ci.com/github/lovell/sharp)
libvips must be installed before `npm install` is run.
This can be achieved via package or ports:
The `vips` package must be installed before `npm install` is run.
```sh
pkg install -y pkgconf vips
@@ -120,175 +112,51 @@ pkg install -y pkgconf vips
cd /usr/ports/graphics/vips/ && make install clean
```
FreeBSD's gcc v4 and v5 need `CXXFLAGS=-D_GLIBCXX_USE_C99` set for C++11 support due to
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
## Heroku
### Heroku
Add the
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc)
to reduce the effects of memory fragmentation.
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
Set
[NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager.
To reduce the effects of memory fragmentation, add the
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc).
## AWS Lambda
### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains an
[Ubuntu-based Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
```sh
docker pull marcbachmann/libvips
```
[Will Jordan](https://github.com/wjordan) maintains an
[Alpine-based Dockerfile for libvips](https://github.com/wjordan/dockerfile-libvips).
```sh
docker pull wjordan/libvips
```
[Tailor Brands](https://github.com/TailorBrands) maintain
[Debian-based Dockerfiles for libvips and nodejs](https://github.com/TailorBrands/docker-libvips).
```sh
docker pull tailor/docker-libvips
```
### AWS Lambda
Set the Lambda runtime to `nodejs10.x`.
Set the Lambda runtime to `nodejs12.x`.
The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html)
must be for the Linux x64 platform/architecture.
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform.
On non-Linux machines such as OS X and Windows run the following:
On machines other than Linux x64, such as macOS and Windows, run the following:
```sh
rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux --target=10.15.0 sharp
npm install --arch=x64 --platform=linux sharp
```
Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh
rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs10.x npm install sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
```
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
### NW.js
## Electron
Run the `nw-gyp` tool after installation.
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
cd node-modules/sharp
nw-gyp rebuild --arch=x64 --target=[your nw version]
node node_modules/sharp/install/dll-copy
npm install
npx electron-rebuild
```
[http://docs.nwjs.io/en/latest/For%20Users/Advanced/Use%20Native%20Node%20Modules/](http://docs.nwjs.io/en/latest/For%20Users/Advanced/Use%20Native%20Node%20Modules/)
### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
### Coding tools
* [Sharp TypeScript Types](https://www.npmjs.com/package/@types/sharp)
### CLI tools
* [sharp-cli](https://www.npmjs.com/package/sharp-cli)
### Security
Many users of this module process untrusted, user-supplied images,
but there are aspects of security to consider when doing so.
It is possible to compile libvips with support for various third-party image loaders.
Each of these libraries has undergone differing levels of security testing.
Whilst tools such as [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/)
and [Valgrind](http://valgrind.org/) have been used to test
the most popular web-based formats, as well as libvips itself,
you are advised to perform your own testing and sandboxing.
### Pre-compiled libvips binaries
This module will attempt to download a pre-compiled bundle of libvips
and its dependencies on Linux and Windows machines under either of these
conditions:
1. If a global installation of libvips that meets the
minimum version requirement cannot be found;
1. If `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable is set.
```sh
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install sharp
```
Should you need to manually download and inspect these files,
you can do so via
[https://github.com/lovell/sharp-libvips/releases](https://github.com/lovell/sharp-libvips/releases)
Should you wish to install these from your own location,
set the `sharp_dist_base_url` npm config option, e.g.
```sh
npm config set sharp_dist_base_url "https://hostname/path/"
npm install sharp
```
or set the `SHARP_DIST_BASE_URL` environment variable, e.g.
```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
To install the prebuilt sharp binaries from a custom URL, please see
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
### Licences
This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
The libraries downloaded and used by this module
are done so under the terms of the following licences,
all of which are compatible with the Apache 2.0 Licence.
Use of libraries under the terms of the LGPLv3 is via the
"any later version" clause of the LGPLv2 or LGPLv2.1.
| Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) |
| freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| fribidi | LGPLv3 |
| gettext | LGPLv3 |
| giflib | MIT Licence |
| glib | LGPLv3 |
| harfbuzz | MIT Licence |
| lcms | MIT Licence |
| libcroco | LGPLv3 |
| libexif | LGPLv3 |
| libffi | MIT Licence |
| libgsf | LGPLv3 |
| libjpeg-turbo | [zlib License, IJG License](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/LICENSE.md) |
| libpng | [libpng License](http://www.libpng.org/pub/png/src/libpng-LICENSE.txt) |
| librsvg | LGPLv3 |
| libtiff | [libtiff License](http://www.libtiff.org/misc.html) (BSD-like) |
| libvips | LGPLv3 |
| libwebp | New BSD License |
| libxml2 | MIT Licence |
| pango | LGPLv3 |
| pixman | MIT Licence |
| zlib | [zlib Licence](https://github.com/madler/zlib/blob/master/zlib.h) |
Further help can be found at
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)

View File

@@ -1,44 +1,46 @@
# Performance
### Test environment
A test to benchmark the performance of this module relative to alternatives.
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
* Node.js v12.10.0
## The contenders
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.8.4 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [jimp](https://www.npmjs.com/package/jimp) v0.9.3 - 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.
* [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.
* sharp v0.23.1 / libvips v8.8.1 - Caching within libvips disabled to ensure a fair comparison.
* sharp v0.24.0 / libvips v8.9.0 - Caching within libvips disabled to ensure a fair comparison.
### The task
## The task
Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available),
then compress to JPEG at a "quality" setting of 80.
### Results
## Test environment
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
* Node.js v12.14.1
## Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.66 | 1.0 |
| mapnik | buffer | buffer | 3.31 | 5.0 |
| gm | buffer | buffer | 3.79 | 5.7 |
| gm | file | file | 3.82 | 5.8 |
| imagemagick | file | file | 4.17 | 6.3 |
| sharp | stream | stream | 25.81 | 39.1 |
| sharp | file | file | 26.76 | 40.5 |
| sharp | buffer | buffer | 28.06 | 42.5 |
| jimp | buffer | buffer | 0.72 | 1.0 |
| mapnik | buffer | buffer | 3.02 | 4.2 |
| gm | buffer | buffer | 3.90 | 5.4 |
| gm | file | file | 3.94 | 5.5 |
| imagemagick | file | file | 4.30 | 6.0 |
| sharp | stream | stream | 23.65 | 32.8 |
| sharp | file | file | 24.66 | 34.3 |
| sharp | buffer | buffer | 25.14 | 34.9 |
Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Benchmark test prerequisites
## Running the benchmark test
Requires _ImageMagick_, _GraphicsMagick_ and _Mapnik_:
@@ -56,8 +58,6 @@ sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-devel
```
### Running the benchmark test
```sh
git clone https://github.com/lovell/sharp.git
cd sharp

2
docs/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

1
docs/search-index.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
'use strict';
const fs = require('fs');
const { extractDescription, extractKeywords } = require('./extract');
const searchIndex = [];
// Install
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
const matches = contents.matchAll(
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
);
for (const match of matches) {
const { title, firstparagraph } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});
fs.writeFileSync(
`${__dirname}/../search-index.json`,
JSON.stringify(searchIndex)
);

View File

@@ -0,0 +1,80 @@
'use strict';
const stopWords = [
'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) =>
str
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.trim();
const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
.join(' ');
module.exports = { extractDescription, extractKeywords };

View File

@@ -6,7 +6,8 @@ const path = require('path');
const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
if (process.platform === 'win32') {
const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`);

View File

@@ -14,8 +14,14 @@ const agent = require('../lib/agent');
const libvips = require('../lib/libvips');
const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`;
const minimumGlibcVersionByArch = {
arm: '2.28',
arm64: '2.29',
x64: '2.17'
};
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersionLabelled}/`;
const fail = function (err) {
npmLog.error('sharp', err.message);
@@ -23,7 +29,7 @@ const fail = function (err) {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
}
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1);
};
@@ -57,17 +63,16 @@ try {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.17.0')) {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz';
@@ -84,7 +89,7 @@ try {
if (err) {
fail(err);
} else if (response.statusCode === 404) {
fail(new Error(`Prebuilt libvips binaries are not yet available for ${platformAndArch}`));
fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`));
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {

View File

@@ -32,6 +32,8 @@ function removeAlpha () {
/**
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
*
* @since 0.21.2
*
* @example
* sharp('rgb.jpg')
* .ensureAlpha()
@@ -52,12 +54,13 @@ function ensureAlpha () {
* @example
* sharp(input)
* .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) {
* .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // info.channels === 1
* // input_green.jpg contains the green channel of the input image
* // green.jpg is a greyscale image containing the green channel of the input
* });
*
* @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
* @param {number|string} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
* @returns {Sharp}
* @throws {Error} Invalid channel
*/
@@ -88,7 +91,7 @@ function extractChannel (channel) {
* Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
* 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).
* @param {Object} options - image options, see `sharp()` constructor.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -116,7 +119,7 @@ function joinChannel (images, options) {
* // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
* });
*
* @param {String} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {string} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/

View File

@@ -19,7 +19,7 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance.
* An alpha channel may be present and will be unchanged by the operation.
*
* @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp}
* @throws {Error} Invalid parameter
*/
@@ -57,7 +57,7 @@ function grayscale (grayscale) {
/**
* Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
* @param {String} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -71,7 +71,7 @@ function toColourspace (colourspace) {
/**
* Alternative spelling of `toColourspace`.
* @param {String} [colorspace] - output colorspace.
* @param {string} [colorspace] - output colorspace.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -82,8 +82,8 @@ function toColorspace (colorspace) {
/**
* Update a colour attribute of the this.options Object.
* @private
* @param {String} key
* @param {String|Object} value
* @param {string} key
* @param {string|Object} value
* @throws {Error} Invalid value
*/
function _setBackgroundColourOption (key, value) {

View File

@@ -53,6 +53,8 @@ const blend = {
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
* and https://www.cairographics.org/operators/
*
* @since 0.22.0
*
* @example
* sharp('input.png')
* .rotate(180)
@@ -70,7 +72,7 @@ const blend = {
* });
*
* @param {Object[]} images - Ordered list of images to composite
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see bellow)
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see below)
* @param {Object} [images[].input.create] - describes a blank overlay to be created.
* @param {Number} [images[].input.create.width]
* @param {Number} [images[].input.create.height]
@@ -98,8 +100,7 @@ function composite (images) {
if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image);
}
const { raw, density } = image;
const inputOptions = (raw || density) ? { raw, density } : undefined;
const inputOptions = this._inputOptionsFromObject(image);
const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over',

View File

@@ -2,15 +2,13 @@
const util = require('util');
const stream = require('stream');
const events = require('events');
const is = require('./is');
require('./libvips').hasVendoredLibvips();
let sharp;
/* istanbul ignore next */
try {
sharp = require('../build/Release/sharp.node');
require('../build/Release/sharp.node');
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
@@ -18,22 +16,29 @@ try {
help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
} else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Run "brew update && brew upgrade vips"');
} else if (/Cannot find module/.test(err.message)) {
help.push('- Run "npm rebuild --verbose sharp" and look for errors');
} else {
help.push('- Remove the "node_modules/sharp" directory, run "npm install" and look for errors');
help.push(
'- Remove the "node_modules/sharp" directory then run',
' "npm install --ignore-scripts=false --verbose" and look for errors'
);
}
help.push(
'- Consult the installation documentation at https://sharp.pixelplumbing.com/en/stable/install/',
'- Consult the installation documentation at https://sharp.pixelplumbing.com/install',
'- Search for this error at https://github.com/lovell/sharp/issues', ''
);
console.error(help.join('\n'));
process.exit(1);
const error = help.join('\n');
throw new Error(error);
}
// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');
/**
* @class Sharp
* @constructs sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
*
@@ -83,6 +88,11 @@ const debuglog = util.debuglog('sharp');
* @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.
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {Number|Boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* 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.
* 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.pages=1] - number of pages to extract for multi-page input (GIF, 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.
@@ -107,9 +117,6 @@ const Sharp = function (input, options) {
}
stream.Duplex.call(this);
this.options = {
// input options
sequentialRead: false,
limitInputPixels: Math.pow(0x3FFF, 2),
// resize options
topOffsetPre: -1,
leftOffsetPre: -1,
@@ -156,7 +163,7 @@ const Sharp = function (input, options) {
gamma: 0,
gammaOut: 0,
greyscale: false,
normalise: 0,
normalise: false,
brightness: 1,
saturation: 1,
hue: 0,
@@ -212,6 +219,11 @@ const Sharp = function (input, options) {
heifCompression: 'hevc',
tileSize: 256,
tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
linearA: 1,
@@ -220,7 +232,7 @@ const Sharp = function (input, options) {
debuglog: debuglog,
// Function to notify of queue length changes
queueListener: function (queueLength) {
queue.emit('change', queueLength);
Sharp.queue.emit('change', queueLength);
}
};
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
@@ -229,38 +241,84 @@ const Sharp = function (input, options) {
util.inherits(Sharp, stream.Duplex);
/**
* An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread
* - complete
* @member
* Take a "snapshot" of the Sharp instance, returning a new instance.
* Cloned instances inherit the input of their parent instance.
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
*
* @example
* sharp.queue.on('change', function(queueLength) {
* console.log('Queue contains ' + queueLength + ' task(s)');
* const pipeline = sharp().rotate();
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
*
* @example
* // Create a pipeline that will download an image, resize it and format it to different files
* // Using Promises to know when the pipeline is complete
* const fs = require("fs");
* const got = require("got");
* const sharpStream = sharp({
* failOnError: false
* });
*
* const promises = [];
*
* promises.push(
* sharpStream
* .clone()
* .jpeg({ quality: 100 })
* .toFile("originalFile.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .jpeg({ quality: 80 })
* .toFile("optimized-500.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .webp({ quality: 80 })
* .toFile("optimized-500.webp")
* );
*
* // https://github.com/sindresorhus/got#gotstreamurl-options
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
*
* Promise.all(promises)
* .then(res => { console.log("Done!", res); })
* .catch(err => {
* console.error("Error processing files, let's clean it up", err);
* try {
* fs.unlinkSync("originalFile.jpg");
* fs.unlinkSync("optimized-500.jpg");
* fs.unlinkSync("optimized-500.webp");
* } catch (e) {}
* });
*
* @returns {Sharp}
*/
const queue = new events.EventEmitter();
Sharp.queue = queue;
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* @example
* console.log(sharp.format);
* @returns {Object}
*/
Sharp.format = sharp.format();
/**
* An Object containing the version numbers of libvips and its dependencies.
* @member
* @example
* console.log(sharp.versions);
*/
Sharp.versions = {
vips: sharp.libvipsVersion()
};
try {
Sharp.versions = require('../vendor/versions.json');
} catch (err) {}
function clone () {
// Clone existing options
const clone = this.constructor.call();
clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input
if (this._isStreamInput()) {
this.on('finish', () => {
// Clone inherits input data
this._flattenBufferIn();
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
}
return clone;
}
Object.assign(Sharp.prototype, { clone });
/**
* Export constructor.

View File

@@ -4,12 +4,27 @@ const color = require('color');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError }
: undefined;
}
/**
* Create Object containing input and input-related options.
* @private
*/
function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = { failOnError: true };
const inputDescriptor = {
failOnError: true,
limitInputPixels: Math.pow(0x3FFF, 2),
sequentialRead: false
};
if (is.string(input)) {
// filesystem
inputDescriptor.file = input;
@@ -19,15 +34,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) {
// Raw Stream
if (_inputOptionsFromObject(inputOptions)) {
// Stream with options
inputDescriptor.buffer = [];
}
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream without options
inputDescriptor.buffer = [];
} else {
throw new Error('Unsupported input ' + typeof input);
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
}`);
}
if (is.object(inputOptions)) {
// Fail on error
@@ -46,6 +63,26 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
}
}
// limitInputPixels
if (is.defined(inputOptions.limitInputPixels)) {
if (is.bool(inputOptions.limitInputPixels)) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
? Math.pow(0x3FFF, 2)
: 0;
} else if (is.integer(inputOptions.limitInputPixels) && inputOptions.limitInputPixels >= 0) {
inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
}
}
// sequentialRead
if (is.defined(inputOptions.sequentialRead)) {
if (is.bool(inputOptions.sequentialRead)) {
inputDescriptor.sequentialRead = inputOptions.sequentialRead;
} else {
throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
}
}
// Raw pixel input
if (is.defined(inputOptions.raw)) {
if (
@@ -110,7 +147,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* Handle incoming Buffer chunk on Writable Stream.
* @private
* @param {Buffer} chunk
* @param {String} encoding - unused
* @param {string} encoding - unused
* @param {Function} callback
*/
function _write (chunk, encoding, callback) {
@@ -146,43 +183,12 @@ function _flattenBufferIn () {
/**
* Are we expecting Stream-based input?
* @private
* @returns {Boolean}
* @returns {boolean}
*/
function _isStreamInput () {
return Array.isArray(this.options.input.buffer);
}
/**
* Take a "snapshot" of the Sharp instance, returning a new instance.
* Cloned instances inherit the input of their parent instance.
* This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
*
* @example
* const pipeline = sharp().rotate();
* pipeline.clone().resize(800, 600).pipe(firstWritableStream);
* pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
* readableStream.pipe(pipeline);
* // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream
*
* @returns {Sharp}
*/
function clone () {
// Clone existing options
const clone = this.constructor.call();
clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input
if (this._isStreamInput()) {
this.on('finish', () => {
// Clone inherits input data
this._flattenBufferIn();
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
}
return clone;
}
/**
* Fast access to (uncached) image metadata without decoding any compressed image data.
* A `Promise` is returned when `callback` is not provided.
@@ -191,14 +197,16 @@ function clone () {
* - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
* - `density`: Number of pixels per inch (DPI), if present
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
* - `pageHeight`: Number of pixels high each page in a multi-page image will be.
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -280,7 +288,7 @@ function metadata (callback) {
* - `minY` (y-coordinate of one of the pixel where the minimum lies)
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
*
* @example
@@ -333,43 +341,6 @@ function stats (callback) {
}
}
/**
* Do not process input images where the number of pixels (width x height) exceeds this limit.
* Assumes image dimensions contained in the input metadata can be trusted.
* The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
* @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit.
* @returns {Sharp}
* @throws {Error} Invalid limit
*/
function limitInputPixels (limit) {
// if we pass in false we represent the integer as 0 to disable
if (limit === false) {
limit = 0;
} else if (limit === true) {
limit = Math.pow(0x3FFF, 2);
}
if (is.integer(limit) && limit >= 0) {
this.options.limitInputPixels = limit;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer', limit);
}
return this;
}
/**
* An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
* This will reduce memory usage and can improve performance on some systems.
*
* The default behaviour *before* function call is `false`, meaning the libvips access method is not sequential.
*
* @param {Boolean} [sequentialRead=true]
* @returns {Sharp}
*/
function sequentialRead (sequentialRead) {
this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this;
}
/**
* Decorate the Sharp prototype with input-related functions.
* @private
@@ -377,15 +348,13 @@ function sequentialRead (sequentialRead) {
module.exports = function (Sharp) {
Object.assign(Sharp.prototype, {
// Private
_inputOptionsFromObject,
_createInputDescriptor,
_write,
_flattenBufferIn,
_isStreamInput,
// Public
clone,
metadata,
stats,
limitInputPixels,
sequentialRead
stats
});
};

View File

@@ -91,8 +91,8 @@ const inArray = function (val, list) {
/**
* Create an Error with a message relating to an invalid parameter.
*
* @param {String} name - parameter name.
* @param {String} expected - description of the type/value/range expected.
* @param {string} name - parameter name.
* @param {string} expected - description of the type/value/range expected.
* @param {*} actual - the value received.
* @returns {Error} Containing the formatted message.
* @private

View File

@@ -8,8 +8,9 @@ const semver = require('semver');
const platform = require('./platform');
const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || /* istanbul ignore next */
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips;
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = {
encoding: 'utf8',
@@ -93,11 +94,12 @@ const useGlobalLibvips = function () {
};
module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion,
cachePath: cachePath,
globalLibvipsVersion: globalLibvipsVersion,
hasVendoredLibvips: hasVendoredLibvips,
pkgConfigPath: pkgConfigPath,
useGlobalLibvips: useGlobalLibvips,
mkdirSync: mkdirSync
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
globalLibvipsVersion,
hasVendoredLibvips,
pkgConfigPath,
useGlobalLibvips,
mkdirSync
};

View File

@@ -32,9 +32,9 @@ const is = require('./is');
* });
* readableStream.pipe(pipeline);
*
* @param {Number} [angle=auto] angle of rotation.
* @param {number} [angle=auto] angle of rotation.
* @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 {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.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -88,9 +88,9 @@ function flop (flop) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
*
* @param {Number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {Number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {Number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -129,7 +129,7 @@ function sharpen (sigma, flat, jagged) {
/**
* Apply median filter.
* When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size
* @param {number} [size=3] square mask size: size x size
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -150,7 +150,7 @@ function median (size) {
* Blur the image.
* When used without parameters, performs a fast, mild blur of the output image.
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
* @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -173,7 +173,7 @@ function blur (sigma) {
/**
* Merge alpha transparency channel, if any, with a background.
* @param {Object} [options]
* @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp}
*/
function flatten (options) {
@@ -193,8 +193,8 @@ function flatten (options) {
*
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
*
* @param {Number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @param {number} [gamma=2.2] value between 1.0 and 3.0.
* @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -264,11 +264,11 @@ function normalize (normalize) {
* });
*
* @param {Object} kernel
* @param {Number} kernel.width - width of the kernel in pixels.
* @param {Number} kernel.height - width of the kernel in pixels.
* @param {Array<Number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {Number} [kernel.offset=0] - the offset of the kernel in pixels.
* @param {number} kernel.width - width of the kernel in pixels.
* @param {number} kernel.height - width of the kernel in pixels.
* @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -300,7 +300,7 @@ function convolve (kernel) {
/**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
@@ -331,13 +331,13 @@ function threshold (threshold, options) {
* This operation creates an output image where each pixel is the result of
* the selected bitwise boolean `operation` between the corresponding pixels of the input images.
*
* @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file.
* @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
* @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Object} [options]
* @param {Object} [options.raw] - describes operand when using raw pixel data.
* @param {Number} [options.raw.width]
* @param {Number} [options.raw.height]
* @param {Number} [options.raw.channels]
* @param {number} [options.raw.width]
* @param {number} [options.raw.height]
* @param {number} [options.raw.channels]
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -353,8 +353,8 @@ function boolean (operand, operator, options) {
/**
* Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset
* @param {number} [a=1.0] multiplier
* @param {number} [b=0.0] offset
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -379,6 +379,8 @@ function linear (a, b) {
/**
* Recomb the image with the specified matrix.
*
* @since 0.21.1
*
* @example
* sharp(input)
* .recomb([
@@ -392,7 +394,7 @@ function linear (a, b) {
* // With this example input, a sepia filter has been applied
* });
*
* @param {Array<Array<Number>>} 3x3 Recombination matrix
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -416,6 +418,8 @@ function recomb (inputMatrix) {
/**
* Transforms the image using brightness, saturation and hue rotation.
*
* @since 0.22.1
*
* @example
* sharp(input)
* .modulate({
@@ -436,9 +440,9 @@ function recomb (inputMatrix) {
* });
*
* @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation
* @param {number} [options.brightness] Brightness multiplier
* @param {number} [options.saturation] Saturation multiplier
* @param {number} [options.hue] Degrees for hue rotation
* @returns {Sharp}
*/
function modulate (options) {

View File

@@ -3,6 +3,17 @@
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['webp', 'webp']
]);
/**
* Write output image data to a file.
*
@@ -25,7 +36,7 @@ const sharp = require('../build/Release/sharp.node');
* .then(info => { ... })
* .catch(err => { ... });
*
* @param {String} fileOut - the path to write the image data to.
* @param {string} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -92,7 +103,7 @@ function toFile (fileOut, callback) {
* .catch(err => { ... });
*
* @param {Object} [options]
* @param {Boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided
*/
@@ -107,9 +118,11 @@ function toBuffer (options, callback) {
/**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
* This will also convert to and add a web-friendly sRGB ICC profile.
*
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
*
* @example
* sharp('input.jpg')
* .withMetadata()
@@ -117,7 +130,7 @@ function toBuffer (options, callback) {
* .then(info => { ... });
*
* @param {Object} [options]
* @param {Number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -135,6 +148,28 @@ function withMetadata (options) {
return this;
}
/**
* Force output to a given format.
*
* @example
* // Convert any input to PNG output
* const data = await sharp(input)
* .toFormat('png')
* .toBuffer();
*
* @param {(string|Object)} format - as a string or an Object with an 'id' attribute
* @param {Object} options - output options
* @returns {Sharp}
* @throws {Error} unsupported format or options
*/
function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
}
return this[actualFormat](options);
}
/**
* Use these JPEG options for output image.
*
@@ -148,18 +183,18 @@ function withMetadata (options) {
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {String} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {string} [options.chromaSubsampling='4:2:0'] - for quality < 90, set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' (use chroma subsampling); for quality >= 90 chroma is never subsampled
* @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
* @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -225,15 +260,15 @@ function jpeg (options) {
* .toBuffer();
*
* @param {Object} [options]
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
* @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
* @param {number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
* @param {number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
* @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -293,13 +328,13 @@ function png (options) {
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {Boolean} [options.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {Number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -351,17 +386,17 @@ function webp (options) {
* .then(info => { ... });
*
* @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {boolean} [options.pyramid=false] - write an image pyramid
* @param {boolean} [options.tile=false] - write a tiled tiff
* @param {boolean} [options.tileWidth=256] - horizontal tile size
* @param {boolean} [options.tileHeight=256] - vertical tile size
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {boolean} [options.squash=false] - squash 8-bit images down to 1 bit
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -444,10 +479,12 @@ function tiff (options) {
*
* Most versions of libheif support only the patent-encumbered HEVC compression format.
*
* @since 0.23.0
*
* @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
* @param {Boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
* @param {boolean} [options.lossless=false] - use lossless compression
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -482,7 +519,9 @@ function heif (options) {
}
/**
* Force output to be raw, uncompressed uint8 pixel data.
* Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
* Pixel ordering is left-to-right, top-to-bottom, without padding.
* Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
*
* @example
* // Extract raw RGB pixel data from JPEG input
@@ -490,45 +529,21 @@ function heif (options) {
* .raw()
* .toBuffer({ resolveWithObject: true });
*
* @example
* // Extract alpha channel as raw pixel data from PNG input
* const data = await sharp('input.png')
* .ensureAlpha()
* .extractChannel(3)
* .toColourspace('b-w')
* .raw()
* .toBuffer();
*
* @returns {Sharp}
*/
function raw () {
return this._updateFormatOut('raw');
}
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['webp', 'webp']
]);
/**
* Force output to a given format.
*
* @example
* // Convert any input to PNG output
* const data = await sharp(input)
* .toFormat('png')
* .toBuffer();
*
* @param {(String|Object)} format - as a String or an Object with an 'id' attribute
* @param {Object} options - output options
* @returns {Sharp}
* @throws {Error} unsupported format or options
*/
function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
}
return this[actualFormat](options);
}
/**
* Use tile-based deep zoom (image pyramid) output.
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
@@ -548,14 +563,14 @@ function toFormat (format, options) {
* });
*
* @param {Object} [options]
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @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.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @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.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -590,10 +605,10 @@ function tile (options) {
}
// Layout
if (is.defined(options.layout)) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'zoomify'])) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
this.options.tileLayout = options.layout;
} else {
throw is.invalidParameterError('layout', 'one of: dz, google, zoomify', options.layout);
throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
}
}
// Angle of rotation,
@@ -638,9 +653,9 @@ function tile (options) {
* Update the output format unless options.force is false,
* in which case revert to input format.
* @private
* @param {String} formatOut
* @param {string} formatOut
* @param {Object} [options]
* @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @returns {Sharp}
*/
function _updateFormatOut (formatOut, options) {
@@ -651,10 +666,10 @@ function _updateFormatOut (formatOut, options) {
}
/**
* Update a Boolean attribute of the this.options Object.
* Update a boolean attribute of the this.options Object.
* @private
* @param {String} key
* @param {Boolean} val
* @param {string} key
* @param {boolean} val
* @throws {Error} Invalid key
*/
function _setBooleanOption (key, val) {
@@ -777,13 +792,13 @@ module.exports = function (Sharp) {
toFile,
toBuffer,
withMetadata,
toFormat,
jpeg,
png,
webp,
tiff,
heif,
raw,
toFormat,
tile,
// Private
_updateFormatOut,

View File

@@ -85,21 +85,30 @@ const mapFitToCanvas = {
outside: 'min'
};
/**
* @private
*/
function isRotationExpected (options) {
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
}
/**
* Resize image to `width`, `height` or `width x height`.
*
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
* - `cover`: Crop to cover both provided dimensions (the default).
* - `contain`: Embed within both provided dimensions.
* - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
*
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
*
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
@@ -173,8 +182,16 @@ const mapFitToCanvas = {
* // and no larger than the input image
* });
*
* @param {Number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @example
* const scaleByHalf = await sharp(input)
* .metadata()
* .then(({ width }) => sharp(input)
* .resize(Math.round(width * 0.5))
* .toBuffer()
* );
*
* @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options]
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
@@ -285,11 +302,11 @@ function resize (width, height, options) {
* })
* ...
*
* @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {Number} [extend.top]
* @param {Number} [extend.left]
* @param {Number} [extend.bottom]
* @param {Number} [extend.right]
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {number} [extend.top]
* @param {number} [extend.left]
* @param {number} [extend.bottom]
* @param {number} [extend.right]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -319,7 +336,7 @@ function extend (extend) {
}
/**
* Extract a region of the image.
* Extract/crop a region of the image.
*
* - Use `extract` before `resize` for pre-resize extraction.
* - Use `extract` after `resize` for post-resize extraction.
@@ -341,10 +358,10 @@ function extend (extend) {
* });
*
* @param {Object} options - describes the region to extract using integral pixel values
* @param {Number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract
* @param {number} options.left - zero-indexed offset from left edge
* @param {number} options.top - zero-indexed offset from top edge
* @param {number} options.width - width of region to extract
* @param {number} options.height - height of region to extract
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -359,7 +376,7 @@ function extract (options) {
}
}, this);
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true || this.options.rotationAngle !== 0)) {
if (suffix === 'Pre' && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this;
@@ -371,7 +388,7 @@ function extract (options) {
*
* The `info` response Object 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}
* @throws {Error} Invalid parameters
*/
@@ -383,6 +400,9 @@ function trim (threshold) {
} else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
}
if (this.options.trimThreshold && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this;
}

View File

@@ -1,8 +1,31 @@
'use strict';
const events = require('events');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* @member
* @example
* console.log(sharp.format);
* @returns {Object}
*/
const format = sharp.format();
/**
* An Object containing the version numbers of libvips and its dependencies.
* @member
* @example
* console.log(sharp.versions);
*/
let versions = {
vips: sharp.libvipsVersion()
};
try {
versions = require('../vendor/versions.json');
} catch (err) {}
/**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
* Existing entries in the cache will be trimmed after any change in limits.
@@ -16,10 +39,10 @@ const sharp = require('../build/Release/sharp.node');
* sharp.cache( { files: 0 } );
* sharp.cache(false);
*
* @param {Object|Boolean} [options=true] - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching
* @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {Number} [options.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache
* @param {Object|boolean} [options=true] - Object with the following attributes, or boolean where true uses default cache settings and false removes all caching
* @param {number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {number} [options.files=20] - is the maximum number of files to hold open
* @param {number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object}
*/
function cache (options) {
@@ -54,13 +77,25 @@ cache(true);
* sharp.concurrency(2); // 2
* sharp.concurrency(0); // 4
*
* @param {Number} [concurrency]
* @returns {Number} concurrency
* @param {number} [concurrency]
* @returns {number} concurrency
*/
function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}
/**
* An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread
* - complete
* @member
* @example
* sharp.queue.on('change', function(queueLength) {
* console.log('Queue contains ' + queueLength + ' task(s)');
* });
*/
const queue = new events.EventEmitter();
/**
* Provides access to internal task counters.
* - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
@@ -89,8 +124,8 @@ function counters () {
* const simd = sharp.simd(false);
* // prevent libvips from using liborc at runtime
*
* @param {Boolean} [simd=true]
* @returns {Boolean}
* @param {boolean} [simd=true]
* @returns {boolean}
*/
function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null);
@@ -110,4 +145,7 @@ module.exports = function (Sharp) {
].forEach(function (f) {
Sharp[f.name] = f;
});
Sharp.format = format;
Sharp.versions = versions;
Sharp.queue = queue;
};

View File

@@ -1,27 +0,0 @@
site_name: sharp
site_url: https://sharp.pixelplumbing.com/
repo_url: https://github.com/lovell/sharp
site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
copyright: <a href="https://pixelplumbing.com/">pixelplumbing.com</a>
google_analytics: ['UA-13034748-12', 'sharp.pixelplumbing.com']
theme: readthedocs
extra_css:
- css/extra.css
markdown_extensions:
- toc:
permalink: True
pages:
- Home: index.md
- Installation: install.md
- API:
- Constructor: api-constructor.md
- Input: api-input.md
- Output: api-output.md
- "Resizing images": api-resize.md
- "Compositing images": api-composite.md
- "Image operations": api-operation.md
- "Colour manipulation": api-colour.md
- "Channel manipulation": api-channel.md
- Utilities: api-utility.md
- Performance: performance.md
- Changelog: changelog.md

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.23.4",
"version": "0.25.3",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -64,23 +64,25 @@
"Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>"
"Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>"
],
"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 --runtime=napi) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci",
"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-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh",
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
"docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
"docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
},
"main": "lib/index.js",
"files": [
"binding.gyp",
"docs/**",
"!docs/css/**",
"install/**",
"lib/**",
"src/**"
@@ -108,40 +110,45 @@
"dependencies": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"nan": "^2.14.0",
"node-addon-api": "^3.0.0",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.3",
"semver": "^6.3.0",
"simple-get": "^3.1.0",
"tar": "^5.0.5",
"semver": "^7.3.2",
"simple-get": "^4.0.0",
"tar": "^6.0.2",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.1.0",
"async": "^3.2.0",
"cc": "^2.0.1",
"decompress-zip": "^0.3.2",
"documentation": "^12.1.4",
"documentation": "^13.0.0",
"exif-reader": "^1.0.3",
"icc": "^1.0.0",
"license-checker": "^25.0.1",
"mocha": "^6.2.2",
"mock-fs": "^4.10.4",
"nyc": "^14.1.1",
"prebuild": "^9.1.1",
"mocha": "^7.1.2",
"mock-fs": "^4.12.0",
"nyc": "^15.0.1",
"prebuild": "^10.0.0",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.0",
"rimraf": "^3.0.2",
"semistandard": "^14.2.0"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.8.1"
"libvips": "8.9.1"
},
"engines": {
"node": ">=8.5.0"
"node": ">=10"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"binary": {
"napi_versions": [
3
]
},
"semistandard": {
"env": [
"mocha"

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,9 +19,7 @@
#include <queue>
#include <mutex> // NOLINT(build/c++11)
#include <node.h>
#include <node_buffer.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
@@ -30,62 +28,77 @@ using vips::VImage;
namespace sharp {
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr) {
return obj.Has(attr);
}
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
std::string AttrAsStr(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::String>();
}
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr);
std::vector<double> rgba(4);
for (unsigned int i = 0; i < 4; i++) {
rgba[i] = AttrTo<double>(background, i);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Uint32Value();
}
int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value();
}
double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value();
}
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
Napi::Array background = obj.Get(attr).As<Napi::Array>();
std::vector<double> rgba(background.Length());
for (unsigned int i = 0; i < background.Length(); i++) {
rgba[i] = AttrAsDouble(background, i);
}
return rgba;
}
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer);
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = buffer.Length();
descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
}
descriptor->failOnError = AttrTo<bool>(input, "failOnError");
descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input
if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density");
descriptor->density = AttrAsDouble(input, "density");
}
// Raw pixel input
if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
}
// Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages");
descriptor->pages = AttrAsInt32(input, "pages");
}
if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page");
descriptor->page = AttrAsUint32(input, "page");
}
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground");
}
// Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
return descriptor;
}
@@ -197,7 +210,7 @@ namespace sharp {
std::string const loader = load;
if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "Png")) {
} else if (EndsWith(loader, "PngFile")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpFile")) {
imageType = ImageType::WEBP;
@@ -244,7 +257,7 @@ namespace sharp {
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor) {
VImage image;
ImageType imageType;
if (descriptor->isBuffer) {
@@ -264,8 +277,11 @@ namespace sharp {
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("access", descriptor->access)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
@@ -310,8 +326,11 @@ namespace sharp {
if (imageType != ImageType::UNKNOWN) {
try {
vips::VOption *option = VImage::option()
->set("access", accessMethod)
->set("access", descriptor->access)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
@@ -334,6 +353,11 @@ namespace sharp {
}
}
}
// Limit input images to a given number of pixels, where pixels = width * height
if (descriptor->limitInputPixels > 0 &&
static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(descriptor->limitInputPixels)) {
throw vips::VError("Input image exceeds pixel limit");
}
return std::make_tuple(image, imageType);
}
@@ -430,11 +454,9 @@ namespace sharp {
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint) {
if (data != nullptr) {
g_free(data);
}
}
std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
g_free(data);
};
/*
Temporary buffer of warnings

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,23 +19,22 @@
#include <tuple>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 8))
#error libvips version 8.8.0+ is required - see sharp.pixelplumbing.com/page/install
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9))
#error "libvips version 8.9.1+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#error "GCC version 4.6+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see sharp.pixelplumbing.com/page/install#prerequisites
#error "clang version 3.0+ is required for C++11 features - please see https://sharp.pixelplumbing.com/install"
#endif
#endif
@@ -48,6 +47,8 @@ namespace sharp {
std::string file;
char *buffer;
bool failOnError;
int limitInputPixels;
VipsAccess access;
size_t bufferLength;
bool isBuffer;
double density;
@@ -64,6 +65,8 @@ namespace sharp {
InputDescriptor():
buffer(nullptr),
failOnError(TRUE),
limitInputPixels(0x3FFF * 0x3FFF),
access(VIPS_ACCESS_RANDOM),
bufferLength(0),
isBuffer(FALSE),
density(72.0),
@@ -78,23 +81,18 @@ namespace sharp {
createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
};
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) {
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, std::string attr);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
double AttrAsDouble(Napi::Object obj, std::string attr);
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
bool AttrAsBool(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input);
enum class ImageType {
JPEG,
@@ -156,7 +154,7 @@ namespace sharp {
/*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod);
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor);
/*
Does this image have an embedded profile?
@@ -207,7 +205,7 @@ namespace sharp {
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint);
extern std::function<void(void*, char*)> FreeCallback;
/*
Called with warnings from the glib-registered "VIPS" domain

View File

@@ -0,0 +1,178 @@
/* Object part of the VSource and VTarget class
*/
/*
Copyright (C) 1991-2001 The National Gallery
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips8>
#include <vips/debug.h>
/*
#define VIPS_DEBUG
#define VIPS_DEBUG_VERBOSE
*/
VIPS_NAMESPACE_START
VSource
VSource::new_from_descriptor( int descriptor )
{
VipsSource *input;
if( !(input = vips_source_new_from_descriptor( descriptor )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_file( const char *filename )
{
VipsSource *input;
if( !(input = vips_source_new_from_file( filename )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_blob( VipsBlob *blob )
{
VipsSource *input;
if( !(input = vips_source_new_from_blob( blob )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_memory( const void *data,
size_t size )
{
VipsSource *input;
if( !(input = vips_source_new_from_memory( data, size )) )
throw VError();
VSource out( input );
return( out );
}
VSource
VSource::new_from_options( const char *options )
{
VipsSource *input;
if( !(input = vips_source_new_from_options( options )) )
throw VError();
VSource out( input );
return( out );
}
VOption *
VOption::set( const char *name, const VSource value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_SOURCE );
g_value_set_object( &pair->value, value.get_source() );
options.push_back( pair );
return( this );
}
VTarget
VTarget::new_to_descriptor( int descriptor )
{
VipsTarget *output;
if( !(output = vips_target_new_to_descriptor( descriptor )) )
throw VError();
VTarget out( output );
return( out );
}
VTarget
VTarget::new_to_file( const char *filename )
{
VipsTarget *output;
if( !(output = vips_target_new_to_file( filename )) )
throw VError();
VTarget out( output );
return( out );
}
VTarget
VTarget::new_to_memory()
{
VipsTarget *output;
if( !(output = vips_target_new_to_memory()) )
throw VError();
VTarget out( output );
return( out );
}
VOption *
VOption::set( const char *name, const VTarget value )
{
Pair *pair = new Pair( name );
pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_TARGET );
g_value_set_object( &pair->value, value.get_target() );
options.push_back( pair );
return( this );
}
VIPS_NAMESPACE_END

View File

@@ -169,7 +169,7 @@ VOption::set( const char *name, const char *value )
// input image
VOption *
VOption::set( const char *name, VImage value )
VOption::set( const char *name, const VImage value )
{
Pair *pair = new Pair( name );
@@ -592,7 +592,30 @@ VImage
VImage::new_from_buffer( const std::string &buf, const char *option_string,
VOption *options )
{
return( new_from_buffer( buf.c_str(), buf.size(), option_string, options ) );
return( new_from_buffer( buf.c_str(), buf.size(),
option_string, options ) );
}
VImage
VImage::new_from_source( VSource source, const char *option_string,
VOption *options )
{
const char *operation_name;
VImage out;
if( !(operation_name = vips_foreign_find_load_source(
source.get_source() )) ) {
delete options;
throw( VError() );
}
options = (options ? options : VImage::option())->
set( "source", source )->
set( "out", &out );
call_option_string( operation_name, option_string, options );
return( out );
}
VImage
@@ -679,6 +702,26 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
}
}
void
VImage::write_to_target( const char *suffix, VTarget target,
VOption *options ) const
{
char filename[VIPS_PATH_MAX];
char option_string[VIPS_PATH_MAX];
const char *operation_name;
vips__filename_split8( suffix, filename, option_string );
if( !(operation_name = vips_foreign_find_save_target( filename )) ) {
delete options;
throw VError();
}
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
#include "vips-operators.cpp"
std::vector<VImage>

View File

@@ -61,7 +61,7 @@ VInterpolate::new_from_name( const char *name, VOption *options )
}
VOption *
VOption::set( const char *name, VInterpolate value )
VOption::set( const char *name, const VInterpolate value )
{
Pair *pair = new Pair( name );

View File

@@ -1,5 +1,5 @@
// bodies for vips operations
// Wed Apr 24 15:50:21 CEST 2019
// Wed 01 Jan 2020 12:22:12 PM CET
// this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const
@@ -491,6 +491,19 @@ VImage VImage::canny( VOption *options ) const
return( out );
}
VImage VImage::case_image( std::vector<VImage> cases, VOption *options ) const
{
VImage out;
call( "case",
(options ? options : VImage::option())->
set( "index", *this )->
set( "out", &out )->
set( "cases", cases ) );
return( out );
}
VImage VImage::cast( VipsBandFormat format, VOption *options ) const
{
VImage out;
@@ -1615,6 +1628,18 @@ VImage VImage::jpegload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::jpegload_source( VSource source, VOption *options )
{
VImage out;
call( "jpegload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::jpegsave( const char *filename, VOption *options ) const
{
call( "jpegsave",
@@ -1642,6 +1667,14 @@ void VImage::jpegsave_mime( VOption *options ) const
set( "in", *this ) );
}
void VImage::jpegsave_target( VTarget target, VOption *options ) const
{
call( "jpegsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::labelregions( VOption *options ) const
{
VImage mask;
@@ -2286,6 +2319,18 @@ VImage VImage::pngload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::pngload_source( VSource source, VOption *options )
{
VImage out;
call( "pngload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::pngsave( const char *filename, VOption *options ) const
{
call( "pngsave",
@@ -2306,6 +2351,14 @@ VipsBlob *VImage::pngsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::pngsave_target( VTarget target, VOption *options ) const
{
call( "pngsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::ppmload( const char *filename, VOption *options )
{
VImage out;
@@ -2413,6 +2466,30 @@ VImage VImage::radload( const char *filename, VOption *options )
return( out );
}
VImage VImage::radload_buffer( VipsBlob *buffer, VOption *options )
{
VImage out;
call( "radload_buffer",
(options ? options : VImage::option())->
set( "out", &out )->
set( "buffer", buffer ) );
return( out );
}
VImage VImage::radload_source( VSource source, VOption *options )
{
VImage out;
call( "radload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::radsave( const char *filename, VOption *options ) const
{
call( "radsave",
@@ -2433,6 +2510,14 @@ VipsBlob *VImage::radsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::radsave_target( VTarget target, VOption *options ) const
{
call( "radsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::rank( int width, int height, int index, VOption *options ) const
{
VImage out;
@@ -2977,6 +3062,30 @@ VImage VImage::svgload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::svgload_source( VSource source, VOption *options )
{
VImage out;
call( "svgload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::switch_image( std::vector<VImage> tests, VOption *options )
{
VImage out;
call( "switch",
(options ? options : VImage::option())->
set( "out", &out )->
set( "tests", tests ) );
return( out );
}
void VImage::system( const char *cmd_format, VOption *options )
{
call( "system",
@@ -3035,6 +3144,19 @@ VImage VImage::thumbnail_image( int width, VOption *options ) const
return( out );
}
VImage VImage::thumbnail_source( VSource source, int width, VOption *options )
{
VImage out;
call( "thumbnail_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source )->
set( "width", width ) );
return( out );
}
VImage VImage::tiffload( const char *filename, VOption *options )
{
VImage out;
@@ -3059,6 +3181,18 @@ VImage VImage::tiffload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::tiffload_source( VSource source, VOption *options )
{
VImage out;
call( "tiffload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::tiffsave( const char *filename, VOption *options ) const
{
call( "tiffsave",
@@ -3170,6 +3304,18 @@ VImage VImage::webpload_buffer( VipsBlob *buffer, VOption *options )
return( out );
}
VImage VImage::webpload_source( VSource source, VOption *options )
{
VImage out;
call( "webpload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::webpsave( const char *filename, VOption *options ) const
{
call( "webpsave",
@@ -3190,6 +3336,14 @@ VipsBlob *VImage::webpsave_buffer( VOption *options ) const
return( buffer );
}
void VImage::webpsave_target( VTarget target, VOption *options ) const
{
call( "webpsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::worley( int width, int height, VOption *options )
{
VImage out;

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,28 +15,16 @@
#include <numeric>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker {
class MetadataWorker : public Napi::AsyncWorker {
public:
MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:MetadataWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~MetadataWorker() {}
void Execute() {
@@ -46,7 +34,7 @@ class MetadataWorker : public Nan::AsyncWorker {
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
@@ -77,6 +65,12 @@ class MetadataWorker : public Nan::AsyncWorker {
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}
if (image.get_typeof("loop") == G_TYPE_INT) {
baton->loop = image.get_int("loop");
}
if (image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
baton->delay = image.get_array_int("delay");
}
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
baton->pagePrimary = image.get_int("heif-primary");
}
@@ -131,129 +125,115 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Metadata Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
if (baton->input->bufferLength > 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->input->bufferLength)));
}
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
}
if (!baton->chromaSubsampling.empty()) {
Set(info,
New("chromaSubsampling").ToLocalChecked(),
New<v8::String>(baton->chromaSubsampling).ToLocalChecked());
}
Set(info, New("isProgressive").ToLocalChecked(), New<v8::Boolean>(baton->isProgressive));
if (baton->paletteBitDepth > 0) {
Set(info, New("paletteBitDepth").ToLocalChecked(), New<v8::Uint32>(baton->paletteBitDepth));
}
if (baton->pages > 0) {
Set(info, New("pages").ToLocalChecked(), New<v8::Uint32>(baton->pages));
}
if (baton->pageHeight > 0) {
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
}
if (baton->pagePrimary > -1) {
Set(info, New("pagePrimary").ToLocalChecked(), New<v8::Uint32>(baton->pagePrimary));
}
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) {
Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info,
New("exif").ToLocalChecked(),
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iccLength > 0) {
Set(info,
New("icc").ToLocalChecked(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iptcLength > 0) {
Set(info,
New("iptc").ToLocalChecked(),
Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->xmpLength > 0) {
Set(info,
New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->tifftagPhotoshopLength > 0) {
Set(info,
New("tifftagPhotoshop").ToLocalChecked(),
Nan::NewBuffer(baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback, nullptr)
.ToLocalChecked());
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format);
if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength);
}
info.Set("width", baton->width);
info.Set("height", baton->height);
info.Set("space", baton->space);
info.Set("channels", baton->channels);
info.Set("depth", baton->depth);
if (baton->density > 0) {
info.Set("density", baton->density);
}
if (!baton->chromaSubsampling.empty()) {
info.Set("chromaSubsampling", baton->chromaSubsampling);
}
info.Set("isProgressive", baton->isProgressive);
if (baton->paletteBitDepth > 0) {
info.Set("paletteBitDepth", baton->paletteBitDepth);
}
if (baton->pages > 0) {
info.Set("pages", baton->pages);
}
if (baton->pageHeight > 0) {
info.Set("pageHeight", baton->pageHeight);
}
if (baton->loop >= 0) {
info.Set("loop", baton->loop);
}
if (!baton->delay.empty()) {
int i = 0;
Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
for (int const d : baton->delay) {
delay.Set(i++, d);
}
info.Set("delay", delay);
}
if (baton->pagePrimary > -1) {
info.Set("pagePrimary", baton->pagePrimary);
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {
info.Set("orientation", baton->orientation);
}
if (baton->exifLength > 0) {
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
}
if (baton->iccLength > 0) {
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
}
if (baton->iptcLength > 0) {
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
}
if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
}
if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop",
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
}
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
}
private:
MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
metadata(options, callback)
*/
NAN_METHOD(metadata) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value metadata(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#define SRC_METADATA_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -36,6 +36,8 @@ struct MetadataBaton {
int paletteBitDepth;
int pages;
int pageHeight;
int loop;
std::vector<int> delay;
int pagePrimary;
bool hasProfile;
bool hasAlpha;
@@ -62,6 +64,7 @@ struct MetadataBaton {
paletteBitDepth(0),
pages(0),
pageHeight(0),
loop(-1),
pagePrimary(-1),
hasProfile(false),
hasAlpha(false),
@@ -78,6 +81,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {}
};
NAN_METHOD(metadata);
Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,8 +25,7 @@
#include <sys/stat.h>
#include <vips/vips8>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include "common.h"
#include "operations.h"
@@ -46,28 +45,18 @@
#define STAT64_FUNCTION stat64
#endif
class PipelineWorker : public Nan::AsyncWorker {
class PipelineWorker : public Napi::AsyncWorker {
public:
PipelineWorker(
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:PipelineWorker"),
baton(baton), debuglog(debuglog), queueListener(queueListener),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
PipelineWorker(Napi::Function callback, PipelineBaton *baton,
Napi::Function debuglog, Napi::Function queueListener) :
Napi::AsyncWorker(callback),
baton(baton),
debuglog(Napi::Persistent(debuglog)),
queueListener(Napi::Persistent(queueListener)) {}
~PipelineWorker() {}
// libuv worker
void Execute() {
using sharp::HasAlpha;
using sharp::ImageType;
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
// Increment processing task counter
@@ -76,16 +65,8 @@ class PipelineWorker : public Nan::AsyncWorker {
try {
// Open input
vips::VImage image;
ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input, baton->accessMethod);
// Limit input images to a given number of pixels, where pixels = width * height
// Ignore if 0
if (baton->limitInputPixels > 0 &&
static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(baton->limitInputPixels)) {
(baton->err).append("Input image exceeds pixel limit");
return Error();
}
sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
// Calculate angle of rotation
VipsAngle rotation;
@@ -134,6 +115,17 @@ class PipelineWorker : public Nan::AsyncWorker {
std::swap(inputWidth, inputHeight);
}
// If withoutEnlargement is specified,
// Override target width and height if exceeds respective value from input file
if (baton->withoutEnlargement) {
if (baton->width > inputWidth) {
baton->width = inputWidth;
}
if (baton->height > inputHeight) {
baton->height = inputHeight;
}
}
// Scaling calculations
double xfactor = 1.0;
double yfactor = 1.0;
@@ -221,21 +213,6 @@ class PipelineWorker : public Nan::AsyncWorker {
double xresidual = static_cast<double>(xshrink) / xfactor;
double yresidual = static_cast<double>(yshrink) / yfactor;
// Do not enlarge the output if the input width *or* height
// are already less than the required dimensions
if (baton->withoutEnlargement) {
if (inputWidth < baton->width || inputHeight < baton->height) {
xfactor = 1.0;
yfactor = 1.0;
xshrink = 1;
yshrink = 1;
xresidual = 1.0;
yresidual = 1.0;
baton->width = inputWidth;
baton->height = inputHeight;
}
}
// If integral x and y shrink are equal, try to use shrink-on-load for JPEG and WebP,
// but not when applying gamma correction, pre-resize extract or trim
int shrink_on_load = 1;
@@ -248,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
(inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
) {
if (xshrink >= 8 * shrink_on_load_factor) {
@@ -274,12 +251,12 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shrink_on_load > 1) {
// Reload input using shrink-on-load
vips::VOption *option = VImage::option()
->set("access", baton->accessMethod)
->set("access", baton->input->access)
->set("shrink", shrink_on_load)
->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) {
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == ImageType::JPEG) {
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option);
} else {
@@ -288,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
if (inputImageType == ImageType::JPEG) {
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} else {
@@ -311,11 +288,16 @@ class PipelineWorker : public Nan::AsyncWorker {
}
// Ensure we're using a device-independent colour space
if (sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS) {
if (
sharp::HasProfile(image) &&
image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16
) {
// Convert to sRGB using embedded profile
try {
image = image.icc_transform("srgb", VImage::option()
->set("embedded", TRUE)
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL));
} catch(...) {
// Ignore failure of embedded profile
@@ -327,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
// Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) {
if (baton->flatten && sharp::HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour
@@ -363,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldComposite = !baton->composite.empty();
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
if (shouldComposite && !HasAlpha(image)) {
if (shouldComposite && !sharp::HasAlpha(image)) {
image = sharp::EnsureAlpha(image);
}
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
// Premultiply image alpha channel before all transformations to avoid
@@ -423,10 +405,10 @@ class PipelineWorker : public Nan::AsyncWorker {
// Join additional color channels to the image
if (baton->joinChannelIn.size() > 0) {
VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN;
sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i], baton->accessMethod);
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
image = image.bandjoin(joinImage);
}
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
@@ -479,7 +461,7 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->height = image.height();
}
image = image.tilecache(VImage::option()
->set("access", baton->accessMethod)
->set("access", VIPS_ACCESS_RANDOM)
->set("threaded", TRUE));
image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION));
@@ -555,8 +537,8 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shouldComposite) {
for (Composite *composite : baton->composite) {
VImage compositeImage;
ImageType compositeImageType = ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input, baton->accessMethod);
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
// Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
throw vips::VError("Image to composite must have same dimensions or smaller");
@@ -591,7 +573,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
// Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!HasAlpha(compositeImage)) {
if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage);
}
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
@@ -645,8 +627,8 @@ class PipelineWorker : public Nan::AsyncWorker {
// Apply bitwise boolean operation between images
if (baton->boolean != nullptr) {
VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean, baton->accessMethod);
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
}
@@ -710,9 +692,9 @@ class PipelineWorker : public Nan::AsyncWorker {
// Output
if (baton->fileOut.empty()) {
// Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) {
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -734,9 +716,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3);
}
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive)
@@ -751,9 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) {
} else if (baton->formatOut == "webp" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -767,10 +751,12 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) {
} else if (baton->formatOut == "tiff" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -793,8 +779,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == ImageType::HEIF)) {
} else if (baton->formatOut == "heif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
@@ -806,7 +792,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "heif";
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) {
} else if (baton->formatOut == "raw" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
// Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image
@@ -847,9 +834,9 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == ImageType::JPEG)) {
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to file
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -863,9 +850,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to file
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive)
@@ -877,9 +865,9 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("dither", baton->pngDither));
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
(willMatchInput && inputImageType == ImageType::WEBP)) {
(willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to file
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -890,10 +878,11 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == ImageType::TIFF)) {
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3);
}
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
@@ -908,15 +897,12 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres));
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == ImageType::HEIF)) {
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file
#ifdef VIPS_TYPE_FOREIGN_HEIF_COMPRESSION
if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
}
#endif
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality)
@@ -947,9 +933,6 @@ class PipelineWorker : public Nan::AsyncWorker {
};
suffix = AssembleSuffixString(".webp", options);
} else {
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE
|| baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY
? ".jpg" : ".jpeg";
std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
@@ -960,35 +943,33 @@ class PipelineWorker : public Nan::AsyncWorker {
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
};
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options);
}
// Remove alpha channel from tile background if image does not contain an alpha channel
if (!HasAlpha(image)) {
if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back();
}
// Write DZ to file
vips::VOption *options = VImage::option()
->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("skip_blanks", baton->tileSkipBlanks);
->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
options->set("depth", baton->tileDepth);
}
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) {
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
// Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata));
@@ -1000,23 +981,30 @@ class PipelineWorker : public Nan::AsyncWorker {
}
}
} catch (vips::VError const &err) {
(baton->err).append(err.what());
char const *what = err.what();
if (what && what[0]) {
(baton->err).append(what);
} else {
(baton->err).append("Unknown error");
}
}
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Nan::Error(baton->err.data());
} else {
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width;
int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
@@ -1028,50 +1016,40 @@ class PipelineWorker : public Nan::AsyncWorker {
height = baton->heightPost;
}
// Info Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied));
Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->formatOut);
info.Set("width", static_cast<uint32_t>(width));
info.Set("height", static_cast<uint32_t>(height));
info.Set("channels", static_cast<uint32_t>(baton->channels));
info.Set("premultiplied", baton->premultiplied);
if (baton->hasCropOffset) {
Set(info, New("cropOffsetLeft").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetLeft)));
Set(info, New("cropOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetTop)));
info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
}
if (baton->trimThreshold > 0.0) {
Set(info, New("trimOffsetLeft").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetLeft)));
Set(info, New("trimOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetTop)));
info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
}
if (baton->bufferOutLength > 0) {
// Pass ownership of output data to Buffer instance
argv[1] = Nan::NewBuffer(
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr)
.ToLocalChecked();
// Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
argv[2] = info;
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
// Pass ownership of output data to Buffer instance
Napi::Buffer<char> data = Napi::Buffer<char>::New(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
} else {
// Add file size to info
struct STAT64_STRUCT st;
if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(st.st_size)));
info.Set("size", static_cast<uint32_t>(st.st_size));
}
argv[1] = info;
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
}
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
// Delete baton
delete baton->input;
delete baton->boolean;
@@ -1084,29 +1062,16 @@ class PipelineWorker : public Nan::AsyncWorker {
}
delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop();
}
// Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
queueListener->Call(1, queueLength, async_resource);
delete queueListener;
// Return to JavaScript
callback->Call(3, argv, async_resource);
Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
queueListener.Call(Receiver().Value(), { queueLength });
}
private:
PipelineBaton *baton;
Nan::Callback *debuglog;
Nan::Callback *queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
Napi::FunctionReference queueListener;
/*
Calculate the angle of rotation and need-to-flip for the given Exif orientation
@@ -1176,41 +1141,27 @@ class PipelineWorker : public Nan::AsyncWorker {
/*
pipeline(options, output, callback)
*/
NAN_METHOD(pipeline) {
using sharp::HasAttr;
using sharp::AttrTo;
using sharp::AttrAs;
using sharp::AttrAsStr;
using sharp::AttrAsRgba;
using sharp::CreateInputDescriptor;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value pipeline(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
PipelineBaton *baton = new PipelineBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// Input
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ?
VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = AttrTo<int32_t>(options, "limitInputPixels");
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Extract image options
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre");
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre");
baton->widthPre = AttrTo<int32_t>(options, "widthPre");
baton->heightPre = AttrTo<int32_t>(options, "heightPre");
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost");
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost");
baton->widthPost = AttrTo<int32_t>(options, "widthPost");
baton->heightPost = AttrTo<int32_t>(options, "heightPost");
baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
// Output image dimensions
baton->width = AttrTo<int32_t>(options, "width");
baton->height = AttrTo<int32_t>(options, "height");
baton->width = sharp::AttrAsInt32(options, "width");
baton->height = sharp::AttrAsInt32(options, "height");
// Canvas option
std::string canvas = AttrAsStr(options, "canvas");
std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") {
baton->canvas = Canvas::CROP;
} else if (canvas == "embed") {
@@ -1223,213 +1174,199 @@ NAN_METHOD(pipeline) {
baton->canvas = Canvas::IGNORE_ASPECT;
}
// Tint chroma
baton->tintA = AttrTo<double>(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB");
baton->tintA = sharp::AttrAsDouble(options, "tintA");
baton->tintB = sharp::AttrAsDouble(options, "tintB");
// Composite
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked())
.ToLocalChecked().As<v8::Array>();
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length");
for (int i = 0; i < compositeArrayLength; i++) {
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
for (unsigned int i = 0; i < compositeArray.Length(); i++) {
Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
Composite *composite = new Composite;
composite->input = CreateInputDescriptor(AttrAs<v8::Object>(compositeObject, "input"), buffersToPersist);
composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
composite->mode = static_cast<VipsBlendMode>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data()));
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity");
composite->left = AttrTo<int32_t>(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied");
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite);
}
// Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
baton->position = AttrTo<int32_t>(options, "position");
baton->resizeBackground = AttrAsRgba(options, "resizeBackground");
baton->kernel = AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad");
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options
if (HasAttr(options, "joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked())
.ToLocalChecked().As<v8::Object>();
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for (int i = 0; i < joinChannelArrayLength; i++) {
if (options.Has("joinChannelIn")) {
Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
baton->joinChannelIn.push_back(
CreateInputDescriptor(
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
buffersToPersist));
sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
}
}
// Operators
baton->flatten = AttrTo<bool>(options, "flatten");
baton->flattenBackground = AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation");
baton->hue = AttrTo<int32_t>(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat");
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle");
baton->rotationAngle = AttrTo<double>(options, "rotationAngle");
baton->rotationBackground = AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip");
baton->flop = AttrTo<bool>(options, "flop");
baton->extendTop = AttrTo<int32_t>(options, "extendTop");
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom");
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight");
baton->extendBackground = AttrAsRgba(options, "extendBackground");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = sharp::AttrAsInt32(options, "hue");
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsDouble(options, "linearA");
baton->linearB = sharp::AttrAsDouble(options, "linearB");
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = sharp::AttrAsBool(options, "flop");
baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha");
if (HasAttr(options, "boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
if (options.Has("boolean")) {
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
}
if (HasAttr(options, "bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp"));
if (options.Has("bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
}
if (HasAttr(options, "convKernel")) {
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel");
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width");
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height");
baton->convKernelScale = AttrTo<double>(kernel, "scale");
baton->convKernelOffset = AttrTo<double>(kernel, "offset");
if (options.Has("convKernel")) {
Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
v8::Local<v8::Array> kdata = AttrAs<v8::Array>(kernel, "kernel");
Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
for (unsigned int i = 0; i < kernelSize; i++) {
baton->convKernel[i] = AttrTo<double>(kdata, i);
baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
}
}
if (HasAttr(options, "recombMatrix")) {
if (options.Has("recombMatrix")) {
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
v8::Local<v8::Array> recombMatrix = AttrAs<v8::Array>(options, "recombMatrix");
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
for (unsigned int i = 0; i < 9; i++) {
baton->recombMatrix[i] = AttrTo<double>(recombMatrix, i);
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
}
}
baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace"));
baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
baton->colourspace = VIPS_INTERPRETATION_sRGB;
}
// Output
baton->formatOut = AttrAsStr(options, "formatOut");
baton->fileOut = AttrAsStr(options, "fileOut");
baton->withMetadata = AttrTo<bool>(options, "withMetadata");
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation");
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
// Format-specific
baton->jpegQuality = AttrTo<uint32_t>(options, "jpegQuality");
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding");
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive");
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering");
baton->pngPalette = AttrTo<bool>(options, "pngPalette");
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality");
baton->pngColours = AttrTo<uint32_t>(options, "pngColours");
baton->pngDither = AttrTo<double>(options, "pngDither");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres");
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash");
baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
// tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data()));
sharp::AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality");
baton->heifLossless = AttrTo<bool>(options, "heifLossless");
#ifdef VIPS_TYPE_FOREIGN_HEIF_COMPRESSION
sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
AttrAsStr(options, "heifCompression").data()));
#endif
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data()));
// Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->tileBackground = AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
}
std::string tileLayout = AttrAsStr(options, "tileLayout");
if (tileLayout == "google") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
} else if (tileLayout == "zoomify") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY;
} else {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
}
baton->tileFormat = AttrAsStr(options, "tileFormat");
std::string tileDepth = AttrAsStr(options, "tileDepth");
if (tileDepth == "onetile") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE;
} else if (tileDepth == "one") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONE;
} else if (tileDepth == "onepixel") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL;
} else {
// signal that we do not want to pass any value to dzSave
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_LAST;
}
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileContainer = static_cast<VipsForeignDzContainer>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
sharp::AttrAsStr(options, "tileContainer").data()));
baton->tileLayout = static_cast<VipsForeignDzLayout>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
sharp::AttrAsStr(options, "tileLayout").data()));
baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
baton->tileDepth = static_cast<VipsForeignDzDepth>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
sharp::AttrAsStr(options, "tileDepth").data()));
// Force random access for certain operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
baton->trimThreshold > 0.0 || baton->normalise ||
baton->position == 16 || baton->position == 17)) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
if (
baton->trimThreshold > 0.0 ||
baton->normalise ||
baton->position == 16 || baton->position == 17 ||
baton->angle % 360 != 0 ||
fmod(baton->rotationAngle, 360.0) != 0.0 ||
baton->useExifOrientation
) {
baton->input->access = VIPS_ACCESS_RANDOM;
}
}
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog"));
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Function to notify of queue length changes
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
Napi::Function queueListener = options.Get("queueListener").As<Napi::Function>();
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) };
v8::Local<v8::Object> recv = Nan::New<v8::Object>();
Nan::Call(*queueListener, recv, 1, queueLength);
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
queueListener.Call(info.This(), { queueLength });
return info.Env().Undefined();
}

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,12 +19,12 @@
#include <string>
#include <vector>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "./common.h"
NAN_METHOD(pipeline);
Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas {
CROP,
@@ -55,7 +55,6 @@ struct Composite {
struct PipelineBaton {
sharp::InputDescriptor *input;
int limitInputPixels;
std::string formatOut;
std::string fileOut;
void *bufferOut;
@@ -119,7 +118,6 @@ struct PipelineBaton {
int extendRight;
std::vector<double> extendBackground;
bool withoutEnlargement;
VipsAccess accessMethod;
int jpegQuality;
bool jpegProgressive;
std::string jpegChromaSubsampling;
@@ -152,7 +150,7 @@ struct PipelineBaton {
double tiffXres;
double tiffYres;
int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression
VipsForeignHeifCompression heifCompression;
bool heifLossless;
std::string err;
bool withMetadata;
@@ -182,7 +180,6 @@ struct PipelineBaton {
PipelineBaton():
input(nullptr),
limitInputPixels(0),
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
@@ -261,7 +258,7 @@ struct PipelineBaton {
tiffXres(1.0),
tiffYres(1.0),
heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifLossless(false),
withMetadata(false),
withMetadataOrientation(-1),

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
@@ -22,33 +21,30 @@
#include "utilities.h"
#include "stats.h"
NAN_MODULE_INIT(init) {
static void* sharp_vips_init(void*) {
vips_init("sharp");
return nullptr;
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
static GOnce sharp_vips_init_once = G_ONCE_INIT;
g_once(&sharp_vips_init_once, static_cast<GThreadFunc>(sharp_vips_init), nullptr);
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked());
Nan::Set(target, Nan::New("cache").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked());
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked());
Nan::Set(target, Nan::New("counters").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked());
Nan::Set(target, Nan::New("simd").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
Nan::Set(target, Nan::New("stats").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
exports.Set("metadata", Napi::Function::New(env, metadata));
exports.Set("pipeline", Napi::Function::New(env, pipeline));
exports.Set("cache", Napi::Function::New(env, cache));
exports.Set("concurrency", Napi::Function::New(env, concurrency));
exports.Set("counters", Napi::Function::New(env, counters));
exports.Set("simd", Napi::Function::New(env, simd));
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
exports.Set("format", Napi::Function::New(env, format));
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
exports.Set("stats", Napi::Function::New(env, stats));
return exports;
}
NAN_MODULE_WORKER_ENABLED(sharp, init)
NODE_API_MODULE(sharp, init)

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,28 +16,16 @@
#include <vector>
#include <iostream>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "stats.h"
class StatsWorker : public Nan::AsyncWorker {
class StatsWorker : public Napi::AsyncWorker {
public:
StatsWorker(
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:StatsWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~StatsWorker() {}
const int STAT_MIN_INDEX = 0;
@@ -54,15 +42,11 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input, baton->accessMethod);
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
@@ -71,20 +55,23 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats();
int const bands = image.bands();
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(),
stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(),
stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats);
}
// Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) {
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false;
}
}
@@ -100,93 +87,78 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Stats Object
v8::Local<v8::Object> info = New<v8::Object>();
v8::Local<v8::Array> channels = New<v8::Array>();
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
v8::Local<v8::Object> channelStat = New<v8::Object>();
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
Set(channelStat, New("sum").ToLocalChecked(), New<v8::Number>(it->sum));
Set(channelStat, New("squaresSum").ToLocalChecked(), New<v8::Number>(it->squaresSum));
Set(channelStat, New("mean").ToLocalChecked(), New<v8::Number>(it->mean));
Set(channelStat, New("stdev").ToLocalChecked(), New<v8::Number>(it->stdev));
Set(channelStat, New("minX").ToLocalChecked(), New<v8::Number>(it->minX));
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
Set(channels, i, channelStat);
}
Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
// Stats Object
Napi::Object info = Napi::Object::New(env);
Napi::Array channels = Napi::Array::New(env);
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
Napi::Object channelStat = Napi::Object::New(env);
channelStat.Set("min", it->min);
channelStat.Set("max", it->max);
channelStat.Set("sum", it->sum);
channelStat.Set("squaresSum", it->squaresSum);
channelStat.Set("mean", it->mean);
channelStat.Set("stdev", it->stdev);
channelStat.Set("minX", it->minX);
channelStat.Set("minY", it->minY);
channelStat.Set("maxX", it->maxX);
channelStat.Set("maxY", it->maxY);
channels.Set(i, channelStat);
}
info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy);
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
}
private:
StatsBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
stats(options, callback)
*/
NAN_METHOD(stats) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value stats(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->accessMethod = AttrTo<bool>(options, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#define SRC_STATS_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -33,12 +33,8 @@ struct ChannelStats {
int maxX;
int maxY;
ChannelStats():
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0)
, minX(0), minY(0), maxX(0), maxY(0) {}
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
};
@@ -46,7 +42,6 @@ struct ChannelStats {
struct StatsBaton {
// Input
sharp::InputDescriptor *input;
VipsAccess accessMethod;
// Output
std::vector<ChannelStats> channelStats;
@@ -62,6 +57,6 @@ struct StatsBaton {
{}
};
NAN_METHOD(stats);
Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
#include <cmath>
#include <string>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include <vips/vector.h>
@@ -24,183 +23,145 @@
#include "operations.h"
#include "utilities.h"
using v8::Boolean;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using Nan::HandleScope;
using Nan::New;
using Nan::Set;
using Nan::ThrowError;
using Nan::To;
using Nan::Utf8String;
/*
Get and set cache limits
*/
NAN_METHOD(cache) {
HandleScope();
Napi::Value cache(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Set memory limit
if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
if (info[0].IsNumber()) {
vips_cache_set_max_mem(info[0].As<Napi::Number>().Int32Value() * 1048576);
}
// Set file limit
if (info[1]->IsInt32()) {
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
if (info[1].IsNumber()) {
vips_cache_set_max_files(info[1].As<Napi::Number>().Int32Value());
}
// Set items limit
if (info[2]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
if (info[2].IsNumber()) {
vips_cache_set_max(info[2].As<Napi::Number>().Int32Value());
}
// Get memory stats
Local<Object> memory = New<Object>();
Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576))));
Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))));
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))));
Napi::Object memory = Napi::Object::New(env);
memory.Set("current", round(vips_tracked_get_mem() / 1048576));
memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
// Get file stats
Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
Napi::Object files = Napi::Object::New(env);
files.Set("current", vips_tracked_get_files());
files.Set("max", vips_cache_get_max_files());
// Get item stats
Local<Object> items = New<Object>();
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
Napi::Object items = Napi::Object::New(env);
items.Set("current", vips_cache_get_size());
items.Set("max", vips_cache_get_max());
Local<Object> cache = New<Object>();
Set(cache, New("memory").ToLocalChecked(), memory);
Set(cache, New("files").ToLocalChecked(), files);
Set(cache, New("items").ToLocalChecked(), items);
info.GetReturnValue().Set(cache);
Napi::Object cache = Napi::Object::New(env);
cache.Set("memory", memory);
cache.Set("files", files);
cache.Set("items", items);
return cache;
}
/*
Get and set size of thread pool
*/
NAN_METHOD(concurrency) {
HandleScope();
Napi::Value concurrency(const Napi::CallbackInfo& info) {
// Set concurrency
if (info[0]->IsInt32()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust());
if (info[0].IsNumber()) {
vips_concurrency_set(info[0].As<Napi::Number>().Int32Value());
}
// Get concurrency
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get()));
return Napi::Number::New(info.Env(), vips_concurrency_get());
}
/*
Get internal counters (queued tasks, processing tasks)
*/
NAN_METHOD(counters) {
using sharp::counterProcess;
using sharp::counterQueue;
HandleScope();
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
info.GetReturnValue().Set(counters);
Napi::Value counters(const Napi::CallbackInfo& info) {
Napi::Object counters = Napi::Object::New(info.Env());
counters.Set("queue", sharp::counterQueue);
counters.Set("process", sharp::counterProcess);
return counters;
}
/*
Get and set use of SIMD vector unit instructions
*/
NAN_METHOD(simd) {
HandleScope();
Napi::Value simd(const Napi::CallbackInfo& info) {
// Set state
if (info[0]->IsBoolean()) {
vips_vector_set_enabled(To<bool>(info[0]).FromJust());
if (info[0].IsBoolean()) {
vips_vector_set_enabled(info[0].As<Napi::Boolean>().Value());
}
// Get state
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled()));
return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
}
/*
Get libvips version
*/
NAN_METHOD(libvipsVersion) {
HandleScope();
Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
info.GetReturnValue().Set(New(version).ToLocalChecked());
return Napi::String::New(info.Env(), version);
}
/*
Get available input/output file/buffer/stream formats
*/
NAN_METHOD(format) {
HandleScope();
// Attribute names
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>();
Napi::Value format(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Object format = Napi::Object::New(env);
for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
}) {
// Input
Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>();
Set(input, attrFile, hasInputFile);
Set(input, attrBuffer, hasInputBuffer);
Set(input, attrStream, hasInputBuffer);
Napi::Boolean hasInputFile =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
Napi::Boolean hasInputBuffer =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Napi::Object input = Napi::Object::New(env);
input.Set("file", hasInputFile);
input.Set("buffer", hasInputBuffer);
input.Set("stream", hasInputBuffer);
// Output
Local<Boolean> hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>();
Set(output, attrFile, hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer);
Napi::Boolean hasOutputFile =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
Napi::Boolean hasOutputBuffer =
Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Napi::Object output = Napi::Object::New(env);
output.Set("file", hasOutputFile);
output.Set("buffer", hasOutputBuffer);
output.Set("stream", hasOutputBuffer);
// Other attributes
Local<Object> container = New<Object>();
Local<String> formatId = New(f).ToLocalChecked();
Set(container, attrId, formatId);
Set(container, attrInput, input);
Set(container, attrOutput, output);
Napi::Object container = Napi::Object::New(env);
container.Set("id", f);
container.Set("input", input);
container.Set("output", output);
// Add to set of formats
Set(format, formatId, container);
format.Set(f, container);
}
// Raw, uncompressed data
Local<Object> raw = New<Object>();
Local<String> rawId = New("raw").ToLocalChecked();
Set(raw, attrId, rawId);
Set(format, rawId, raw);
Local<Boolean> supported = New<Boolean>(true);
Local<Boolean> unsupported = New<Boolean>(false);
Local<Object> rawInput = New<Object>();
Set(rawInput, attrFile, unsupported);
Set(rawInput, attrBuffer, supported);
Set(rawInput, attrStream, supported);
Set(raw, attrInput, rawInput);
Local<Object> rawOutput = New<Object>();
Set(rawOutput, attrFile, unsupported);
Set(rawOutput, attrBuffer, supported);
Set(rawOutput, attrStream, supported);
Set(raw, attrOutput, rawOutput);
Napi::Boolean supported = Napi::Boolean::New(env, true);
Napi::Boolean unsupported = Napi::Boolean::New(env, false);
Napi::Object rawInput = Napi::Object::New(env);
rawInput.Set("file", unsupported);
rawInput.Set("buffer", supported);
rawInput.Set("stream", supported);
Napi::Object rawOutput = Napi::Object::New(env);
rawOutput.Set("file", unsupported);
rawOutput.Set("buffer", supported);
rawOutput.Set("stream", supported);
Napi::Object raw = Napi::Object::New(env);
raw.Set("id", "raw");
raw.Set("input", rawInput);
raw.Set("output", rawOutput);
format.Set("raw", raw);
info.GetReturnValue().Set(format);
return format;
}
/*
@@ -208,65 +169,59 @@ NAN_METHOD(format) {
Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using vips::VImage;
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::HasAlpha;
HandleScope();
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Open input files
VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) {
sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As<Napi::String>().Utf8Value().data());
if (imageType1 != sharp::ImageType::UNKNOWN) {
try {
image1 = VImage::new_from_file(*Utf8String(info[0]));
image1 = VImage::new_from_file(info[0].As<Napi::String>().Utf8Value().c_str());
} catch (...) {
return ThrowError("Input file 1 has corrupt header");
throw Napi::Error::New(env, "Input file 1 has corrupt header");
}
} else {
return ThrowError("Input file 1 is of an unsupported image format");
throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
}
VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) {
sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As<Napi::String>().Utf8Value().data());
if (imageType2 != sharp::ImageType::UNKNOWN) {
try {
image2 = VImage::new_from_file(*Utf8String(info[1]));
image2 = VImage::new_from_file(info[1].As<Napi::String>().Utf8Value().c_str());
} catch (...) {
return ThrowError("Input file 2 has corrupt header");
throw Napi::Error::New(env, "Input file 2 has corrupt header");
}
} else {
return ThrowError("Input file 2 is of an unsupported image format");
throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
}
// Ensure same number of channels
if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands");
throw Napi::Error::New(env, "mismatchedBands");
}
// Ensure same dimensions
if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions");
throw Napi::Error::New(env, "mismatchedDimensions");
}
double maxColourDistance;
try {
// Premultiply and remove alpha
if (HasAlpha(image1)) {
if (sharp::HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
}
if (HasAlpha(image2)) {
if (sharp::HasAlpha(image2)) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
}
// Calculate colour distance
maxColourDistance = image1.dE00(image2).max();
} catch (VError const &err) {
return ThrowError(err.what());
} catch (vips::VError const &err) {
throw Napi::Error::New(env, err.what());
}
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance));
return Napi::Number::New(env, maxColourDistance);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,14 +15,14 @@
#ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_
#include <nan.h>
#include <napi.h>
NAN_METHOD(cache);
NAN_METHOD(concurrency);
NAN_METHOD(counters);
NAN_METHOD(simd);
NAN_METHOD(libvipsVersion);
NAN_METHOD(format);
NAN_METHOD(_maxColourDistance);
Napi::Value cache(const Napi::CallbackInfo& info);
Napi::Value concurrency(const Napi::CallbackInfo& info);
Napi::Value counters(const Napi::CallbackInfo& info);
Napi::Value simd(const Napi::CallbackInfo& info);
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
Napi::Value format(const Napi::CallbackInfo& info);
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_

View File

@@ -12,9 +12,9 @@
"benchmark": "^2.1.4",
"gm": "^1.23.1",
"imagemagick": "^0.1.3",
"jimp": "^0.8.4",
"jimp": "^0.9.3",
"mapnik": "^4.3.1",
"semver": "^6.3.0"
"semver": "^7.1.1"
},
"license": "Apache-2.0",
"engines": {

View File

@@ -460,8 +460,7 @@ async.series({
}).add('sharp-sequentialRead', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.sequentialRead()
sharp(inputJpgBuffer, { sequentialRead: true })
.resize(width, height)
.toBuffer(function (err, buffer) {
if (err) {

BIN
test/fixtures/16-bit-grey-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
test/fixtures/animated-loop-3.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -79,6 +79,7 @@ module.exports = {
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
inputPng16BitGreyAlpha: getPath('16-bit-grey-alpha.png'), // CC-BY-NC-SA florc http://www.colourlovers.com/pattern/50713/pat
inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
@@ -101,6 +102,7 @@ module.exports = {
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
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
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg

View File

@@ -5,7 +5,7 @@ if ! type valgrind >/dev/null; then
exit 1
fi
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/libvips.supp
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
for test in ./test/unit/*.js; do
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \

View File

@@ -196,6 +196,18 @@
...
fun:FcInitLoadConfigAndFonts
}
{
leak_fontconfig_doContent
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:doContent
fun:doProlog
fun:prologInitProcessor
fun:XML_ParseBuffer
obj:*/libfontconfig.so.*
}
# libvips
{
@@ -342,6 +354,12 @@
...
obj:/usr/bin/iojs
}
{
value_node_invoke_params
Memcheck:Value8
...
fun:_ZN2v88internal12_GLOBAL__N_16InvokeEPNS0_7IsolateERKNS1_12InvokeParamsE
}
{
leak_nodejs_ImmutableAsciiSource_CreateFromLiteral
Memcheck:Leak
@@ -538,6 +556,13 @@
...
fun:_ZN4node9inspector5Agent5StartERKSsSt10shared_ptrINS_12DebugOptionsEEb
}
{
leak_nodejs_debug_host_port
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node9inspector5Agent5StartERKSsRKNS_12DebugOptionsESt10shared_ptrINS_8HostPortEEb
}
{
leak_nodejs_start
Memcheck:Leak
@@ -553,11 +578,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei
}
{
leak_nan_FunctionCallbackInfo
leak_napi_module_register
Memcheck:Leak
match-leak-kinds: definite
...
fun:_ZN3Nan3impL23FunctionCallbackWrapperERKN2v820FunctionCallbackInfoINS1_5ValueEEE
fun:napi_module_register
fun:call_init.part.0
fun:call_init
fun:_dl_init
}
{
leak_v8_FunctionCallbackInfo

View File

@@ -5,7 +5,10 @@ const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL;
const usingSimd = !process.env.G_DEBUG;
const concurrency = detectLibc.family === detectLibc.MUSL ? 1 : undefined;
const concurrency =
detectLibc.family === detectLibc.MUSL || process.arch === 'arm'
? 1
: undefined;
beforeEach(function () {
sharp.cache(usingCache);

View File

@@ -141,7 +141,7 @@ describe('composite', () => {
it('zero offset', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
top: 0,
@@ -157,7 +157,7 @@ describe('composite', () => {
it('offset and gravity', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,
@@ -174,7 +174,7 @@ describe('composite', () => {
it('offset, gravity and tile', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,
@@ -192,7 +192,7 @@ describe('composite', () => {
it('offset and tile', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,

View File

@@ -48,8 +48,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG', function (done) {
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.strictEqual(data, null);
assert.strictEqual(info, null);
assert.strictEqual(data, undefined);
assert.strictEqual(info, undefined);
done();
});
});
@@ -57,8 +57,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated PNG', function (done) {
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err);
assert.strictEqual(data, null);
assert.strictEqual(info, null);
assert.strictEqual(data, undefined);
assert.strictEqual(info, undefined);
done();
});
});

View File

@@ -214,37 +214,39 @@ describe('Input/output', function () {
});
});
it('Sequential read, force JPEG', function (done) {
sharp(fixtures.inputJpg)
.sequentialRead()
.resize(320, 240)
.toFormat(sharp.format.jpeg)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
it('Invalid sequential read option throws', () => {
assert.throws(() => {
sharp({ sequentialRead: 'fail' });
}, /Expected boolean for sequentialRead but received fail of type string/);
});
it('Not sequential read, force JPEG', function (done) {
sharp(fixtures.inputJpg)
.sequentialRead(false)
it('Sequential read, force JPEG', () =>
sharp(fixtures.inputJpg, { sequentialRead: true })
.resize(320, 240)
.toFormat(sharp.format.jpeg)
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
assert.strictEqual(data.length > 0, true);
assert.strictEqual(data.length, info.size);
assert.strictEqual(info.format, 'jpeg');
assert.strictEqual(info.width, 320);
assert.strictEqual(info.height, 240);
})
);
it('Not sequential read, force JPEG', () =>
sharp(fixtures.inputJpg, { sequentialRead: false })
.resize(320, 240)
.toFormat('jpeg')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
assert.strictEqual(data.length > 0, true);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
assert.strictEqual(info.format, 'jpeg');
assert.strictEqual(info.width, 320);
assert.strictEqual(info.height, 240);
})
);
it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng)
@@ -300,6 +302,7 @@ describe('Input/output', function () {
});
it('Fail when input is empty Buffer', function (done) {
if (process.platform === 'freebsd') return this.skip(); // can be removed with libvips 8.10.0+
sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false);
done();
@@ -559,103 +562,101 @@ describe('Input/output', function () {
});
});
describe('Limit pixel count of input image', function () {
it('Invalid fails - negative', function (done) {
let isValid = false;
try {
sharp().limitInputPixels(-1);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - float', function (done) {
let isValid = false;
try {
sharp().limitInputPixels(12.3);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - string', function (done) {
let isValid = false;
try {
sharp().limitInputPixels('fail');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Same size as input works', function (done) {
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
describe('Limit pixel count of input image', () => {
it('Invalid fails - negative', () => {
assert.throws(() => {
sharp({ limitInputPixels: -1 });
});
});
it('Disabling limit works', function (done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(false)
it('Invalid fails - float', () => {
assert.throws(() => {
sharp({ limitInputPixels: 12.3 });
});
});
it('Invalid fails - string', () => {
assert.throws(() => {
sharp({ limitInputPixels: 'fail' });
});
});
it('Same size as input works', () =>
sharp(fixtures.inputJpg)
.metadata()
.then(({ width, height }) =>
sharp(fixtures.inputJpg, { limitInputPixels: width * height }).toBuffer()
)
);
it('Disabling limit works', () =>
sharp(fixtures.inputJpgLarge, { limitInputPixels: false })
.resize(2)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
});
.toBuffer()
);
it('Enabling default limit works and fails with a large image', function (done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(true)
.toBuffer(function (err) {
assert.strictEqual(true, !!err);
done();
});
});
it('Enabling default limit works and fails with a large image', () =>
sharp(fixtures.inputJpgLarge, { limitInputPixels: true })
.toBuffer()
.then(() => {
assert.fail('Expected to fail');
})
.catch(err => {
assert.strictEqual(err.message, 'Input image exceeds pixel limit');
})
);
it('Smaller than input fails', function (done) {
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels((metadata.width * metadata.height) - 1)
.toBuffer(function (err) {
assert.strictEqual(true, !!err);
done();
});
});
});
it('Smaller than input fails', () =>
sharp(fixtures.inputJpg)
.metadata()
.then(({ width, height }) =>
sharp(fixtures.inputJpg, { limitInputPixels: width * height - 1 })
.toBuffer()
.then(() => {
assert.fail('Expected to fail');
})
.catch(err => {
assert.strictEqual(err.message, 'Input image exceeds pixel limit');
})
)
);
});
describe('Input options', function () {
it('Option-less', function () {
sharp();
});
it('Ignore unknown attribute', function () {
sharp({ unknown: true });
});
it('undefined with options fails', function () {
assert.throws(function () {
sharp(undefined, {});
}, /Unsupported input 'undefined' of type undefined when also providing options of type object/);
});
it('null with options fails', function () {
assert.throws(function () {
sharp(null, {});
}, /Unsupported input 'null' of type object when also providing options of type object/);
});
it('Non-Object options fails', function () {
assert.throws(function () {
sharp(null, 'zoinks');
});
sharp('test', 'zoinks');
}, /Invalid input options zoinks/);
});
it('Invalid density: string', function () {
assert.throws(function () {
sharp(null, { density: 'zoinks' });
});
});
it('Ignore unknown attribute', function () {
sharp(null, { unknown: true });
sharp({ density: 'zoinks' });
}, /Expected number between 1 and 2400 for density but received zoinks of type string/);
});
it('Invalid page property throws', function () {
assert.throws(function () {
sharp(null, { page: -1 });
sharp({ page: -1 });
}, /Expected integer between 0 and 100000 for page but received -1 of type number/);
});
it('Invalid pages property throws', function () {
assert.throws(function () {
sharp(null, { pages: '1' });
sharp({ pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
});
});
@@ -749,7 +750,7 @@ describe('Input/output', function () {
assert.strictEqual(472, info.height);
assert.strictEqual(3, info.channels);
});
const badPipeline = sharp(null, { raw: { width: 840, height: 500, channels: 3 } })
const badPipeline = sharp({ raw: { width: 840, height: 500, channels: 3 } })
.toFormat('jpeg')
.toBuffer(function (err, data, info) {
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
@@ -757,7 +758,7 @@ describe('Input/output', function () {
const inPipeline = sharp()
.resize(840, 472)
.raw();
const goodPipeline = sharp(null, { raw: { width: 840, height: 472, channels: 3 } })
const goodPipeline = sharp({ raw: { width: 840, height: 472, channels: 3 } })
.toFormat('jpeg')
.toBuffer(function (err, data, info) {
if (err) throw err;

View File

@@ -232,6 +232,55 @@ describe('Image metadata', function () {
done();
});
});
it('Animated GIF', () =>
sharp(fixtures.inputGifAnimated)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'gif');
assert.strictEqual(width, 80);
assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 30);
assert.strictEqual(pageHeight, 80);
assert.strictEqual(loop, 0);
assert.deepStrictEqual(delay, Array(30).fill(30));
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('Animated GIF with limited looping', () =>
sharp(fixtures.inputGifAnimatedLoop3)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'gif');
assert.strictEqual(width, 370);
assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 10);
assert.strictEqual(pageHeight, 285);
assert.strictEqual(loop, 3);
assert.deepStrictEqual(delay, [...Array(9).fill(3000), 15000]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('vips', () =>
sharp(fixtures.inputV)
.metadata()

View File

@@ -43,7 +43,10 @@ describe('Platform-detection', function () {
it('Defaults to ARMv6 for 32-bit', function () {
process.env.npm_config_arch = 'arm';
const armVersion = process.config.variables.arm_version;
delete process.config.variables.arm_version;
assert.strictEqual('armv6', platform().split('-')[1]);
process.config.variables.arm_version = armVersion;
delete process.env.npm_config_arch;
});

View File

@@ -103,6 +103,15 @@ describe('PNG', function () {
});
});
it('16-bit grey+alpha PNG identity transform', function () {
const actual = fixtures.path('output.16-bit-grey-alpha-identity.png');
return sharp(fixtures.inputPng16BitGreyAlpha)
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, fixtures.expected('16-bit-grey-alpha-identity.png'));
});
});
it('Valid PNG libimagequant palette value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().png({ palette: false });

View File

@@ -168,5 +168,19 @@ describe('Raw pixel data', function () {
done();
});
});
it('extract A from RGBA', () =>
sharp(fixtures.inputPngWithTransparency)
.resize(32, 24)
.extractChannel(3)
.toColourspace('b-w')
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual('raw', info.format);
assert.strictEqual(1, info.channels);
assert.strictEqual(32 * 24, info.size);
})
);
});
});

View File

@@ -307,6 +307,40 @@ describe('Resize dimensions', function () {
});
});
it('Do crop when fit = cover and withoutEnlargement = true and width >= outputWidth, and height < outputHeight', function (done) {
sharp(fixtures.inputJpg)
.resize({
width: 3000,
height: 1000,
withoutEnlargement: true
})
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(1000, info.height);
done();
});
});
it('Do crop when fit = cover and withoutEnlargement = true and width < outputWidth, and height >= outputHeight', function (done) {
sharp(fixtures.inputJpg)
.resize({
width: 1500,
height: 2226,
withoutEnlargement: true
})
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(1500, info.width);
assert.strictEqual(2225, info.height);
done();
});
});
it('Do enlarge when input width is less than output width', function (done) {
sharp(fixtures.inputJpg)
.resize({

View File

@@ -242,11 +242,11 @@ describe('Image Stats', function () {
// red channel
assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 83291370));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11379783198));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 105.36169496842616));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.39412151419967));
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].squaresSum, 11213984832));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 104.36947963892487));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.379896254993135));
assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[0].minY));
@@ -258,11 +258,11 @@ describe('Image Stats', function () {
// green channel
assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120877425));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20774687595));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 152.9072025279307));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.84143349689916));
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].squaresSum, 20533721114));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 151.90993361398964));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.83370206587037));
assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[1].minY));
@@ -274,11 +274,11 @@ describe('Image Stats', function () {
// blue channel
assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138938859));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28449125593));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 175.75450711423252));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.39929031070358));
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].squaresSum, 28172033081));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 174.76123932359133));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.38276338513747));
assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[2].minY));

View File

@@ -208,11 +208,48 @@ describe('TIFF', function () {
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert.strictEqual(3, info.channels);
assert(info.size < startSize);
rimraf(fixtures.outputTiff, done);
});
});
it('TIFF LZW RGBA toFile', () =>
sharp({
create: {
width: 1,
height: 1,
channels: 4,
background: 'red'
}
})
.tiff({
compression: 'lzw'
})
.toFile(fixtures.outputTiff)
.then(info => {
assert.strictEqual(4, info.channels);
})
);
it('TIFF LZW RGBA toBuffer', () =>
sharp({
create: {
width: 1,
height: 1,
channels: 4,
background: 'red'
}
})
.tiff({
compression: 'lzw'
})
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual(4, info.channels);
})
);
it('TIFF ccittfax4 compression shrinks b-w test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiff).size;
sharp(fixtures.inputTiff)

View File

@@ -765,6 +765,30 @@ describe('Tile', function () {
});
});
it('IIIF layout', function (done) {
const directory = fixtures.path('output.iiif.info');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
layout: 'iiif'
})
.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);
fs.stat(path.join(directory, '0,0,256,256', '256,', '0', 'default.jpg'), function (err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
done();
});
});
});
});
it('Write to ZIP container using file extension', function (done) {
const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container');

View File

@@ -10,8 +10,8 @@ describe('toBuffer', () => {
const image = sharp(fixtures.inputJpg);
image.toBuffer({ resolveWithObject: true }).then((obj) => {
image.toBuffer().then((buff) => {
assert.strict.equal(Buffer.isBuffer(buff), true);
assert.strict.equal(typeof obj, 'object');
assert.strictEqual(Buffer.isBuffer(buff), true);
assert.strictEqual(typeof obj, 'object');
done();
});
});

View File

@@ -94,6 +94,32 @@ describe('Trim borders', function () {
.catch(done);
});
it('should rotate before trim', () =>
sharp({
create: {
width: 20,
height: 30,
channels: 3,
background: 'white'
}
})
.rotate(30)
.png()
.toBuffer()
.then(rotated30 =>
sharp(rotated30)
.rotate(-30)
.trim(128)
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual(20, info.width);
assert.strictEqual(31, info.height);
assert.strictEqual(-8, info.trimOffsetTop);
assert.strictEqual(-13, info.trimOffsetLeft);
})
)
);
describe('Invalid thresholds', function () {
[-1, 'fail', {}].forEach(function (threshold) {
it(JSON.stringify(threshold), function () {