Compare commits

..

41 Commits

Author SHA1 Message Date
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
55 changed files with 1260 additions and 1115 deletions

View File

@@ -44,8 +44,7 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.24.0 | wit | | v0.26.0 | zoom |
| v0.25.0 | yield |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

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

View File

@@ -1,20 +1,20 @@
--- ---
name: Installation name: Installation
about: Something went wrong **installing** sharp about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
title: ''
labels: installation 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? 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 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? 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 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 name: Possible bug
about: Something unexpected occurred **using** sharp about: Installation of sharp was successful but then something unexpected occurred using one of its features
title: ''
labels: triage labels: triage
assignees: ''
--- ---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. --> <!-- 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 are the steps to reproduce?
What is the expected behaviour? 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? 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 name: Question
about: For help understanding an existing feature about: For help understanding an existing feature
title: ''
labels: question labels: question
assignees: ''
--- ---
@@ -13,6 +11,6 @@ What are you trying to achieve?
Have you searched for similar questions? 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? Are you able to provide a sample image that helps explain the question?

View File

@@ -1,75 +1,137 @@
matrix: jobs:
include: include:
- name: "Linux (glibc 2.17+) - Node.js 10" - name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux
dist: xenial
language: node_js
node_js: "10"
- name: "Linux (glibc 2.17+) - Node.js 12"
os: linux
dist: xenial
language: node_js
node_js: "12"
- name: "Linux (glibc 2.17+) - Node.js 13"
os: linux
dist: xenial
language: node_js
node_js: "13"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (musl 1.1.20+) - Node.js 10"
os: linux os: linux
dist: bionic dist: bionic
language: minimal 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_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 (Alpine 3.9, musl 1.1.20) - Node.js 10"
os: linux
dist: bionic
language: shell
before_install: 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 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 - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 12"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: 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 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 - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 13"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 13"
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: 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 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 - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (glibc 2.29+) - Node.js 10"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
arch: arm64 arch: arm64
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye - sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp apt-get update - sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp apt-get install -y build-essential git python2 nodejs npm - 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" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13+) - Node.js 10"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 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: "macOS (10.13) - Node.js 10"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "10" node_js: "10"
- name: "macOS (10.13+) - Node.js 12"
- name: "macOS (10.13) - Node.js 12"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "12" node_js: "12"
- name: "macOS (10.13+) - Node.js 13"
- name: "macOS (10.13) - Node.js 13"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "13" node_js: "13"
- 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: cache:
npm: false npm: false

View File

@@ -20,8 +20,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern macOS, Windows and Linux systems running Node.js v10+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
## Examples ## Examples
@@ -90,10 +89,10 @@ readableStream
### Documentation ### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/page/install), [installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/page/api), [API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and [benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/page/changelog). [changelog](https://sharp.pixelplumbing.com/changelog).
### Contributing ### Contributing

View File

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

View File

@@ -30,6 +30,9 @@
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
@@ -44,7 +47,11 @@
] ]
}, { }, {
'target_name': 'sharp', 'target_name': 'sharp',
'defines': [
'NAPI_VERSION=3'
],
'dependencies': [ 'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp' 'libvips-cpp'
], ],
'variables': { 'variables': {
@@ -65,11 +72,11 @@
'src/stats.cc', 'src/stats.cc',
'src/operations.cc', 'src/operations.cc',
'src/pipeline.cc', 'src/pipeline.cc',
'src/sharp.cc', 'src/utilities.cc',
'src/utilities.cc' 'src/sharp.cc'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!@(node -p "require(\'node-addon-api\').include")',
], ],
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
@@ -189,10 +196,18 @@
'-Wno-cast-function-type' '-Wno-cast-function-type'
] ]
}], }],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', { ['OS == "win"', {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [

View File

@@ -16,12 +16,9 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern macOS, Windows and Linux systems running Node.js v10+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. 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 ### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images. This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
@@ -67,73 +64,6 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md) A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes. 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)
* [Brychan Bennett-Odlum](https://github.com/BrychanOdlum)
Thank you!
### Licensing ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.

View File

@@ -49,9 +49,10 @@ Extract a single channel from a multi-channel image.
```javascript ```javascript
sharp(input) sharp(input)
.extractChannel('green') .extractChannel('green')
.toFile('input_green.jpg', function(err, info) { .toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// info.channels === 1 // 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
}); });
``` ```

View File

@@ -68,7 +68,7 @@ A `Promise` is returned when `callback` is not provided.
- `minY` (y-coordinate of one of the pixel where the minimum lies) - `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum 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) - `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) - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
### Parameters ### Parameters
@@ -88,9 +88,9 @@ image
Returns **[Promise][5]&lt;[Object][6]>** Returns **[Promise][5]&lt;[Object][6]>**
[1]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636 [1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
[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 [3]: https://www.npmjs.com/package/icc

View File

@@ -296,7 +296,9 @@ Returns **Sharp**
## raw ## 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 ### Examples
@@ -307,6 +309,16 @@ const { data, info } = await sharp('input.jpg')
.toBuffer({ resolveWithObject: true }); .toBuffer({ resolveWithObject: true });
``` ```
```javascript
// Extract alpha channel as raw pixel data from PNG input
const data = await sharp('input.png')
.ensureAlpha()
.extractChannel(3)
.colourspace('b-w')
.raw()
.toBuffer();
```
Returns **Sharp** Returns **Sharp**
## tile ## tile
@@ -327,7 +339,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. - `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.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) - `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples ### 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: 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). - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Embed within both provided dimensions. - `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. - `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. - `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. - `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: When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`. - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy. - `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 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. then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.

View File

@@ -1,9 +1,59 @@
# Changelog # Changelog
## v0.25 - *yield*
Requires libvips v8.9.1
### 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*" ## v0.24 - "*wit*"
Requires libvips v8.9.0. 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 ### v0.24.0 - 16<sup>th</sup> January 2020
* Drop support for Node.js 8. * Drop support for Node.js 8.

View File

@@ -25,6 +25,11 @@
"destination": "/install", "destination": "/install",
"type": 301 "type": 301
}, },
{
"source": "/page/install",
"destination": "/install",
"type": 301
},
{ {
"source": "**/api-constructor/**", "source": "**/api-constructor/**",
"destination": "/api-constructor", "destination": "/api-constructor",
@@ -70,16 +75,31 @@
"destination": "/api-utility", "destination": "/api-utility",
"type": 301 "type": 301
}, },
{
"source": "/page/api",
"destination": "/api-constructor",
"type": 301
},
{ {
"source": "**/performance/**", "source": "**/performance/**",
"destination": "/performance", "destination": "/performance",
"type": 301 "type": 301
}, },
{
"source": "/page/performance",
"destination": "/performance",
"type": 301
},
{ {
"source": "**/changelog/**", "source": "**/changelog/**",
"destination": "/changelog", "destination": "/changelog",
"type": 301 "type": 301
}, },
{
"source": "/page/changelog",
"destination": "/changelog",
"type": 301
},
{ {
"source": "/en/**", "source": "/en/**",
"destination": "/", "destination": "/",

181
docs/humans.txt Normal file
View File

@@ -0,0 +1,181 @@
/* 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

View File

@@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg"> <link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/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="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
@@ -12,7 +13,31 @@
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg"> <link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg"> <link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.css"> <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="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> <style>
:root {
--link-color: #077;
}
@media (max-width: 576px) { @media (max-width: 576px) {
.shorten-strapline { .shorten-strapline {
display: none; display: none;
@@ -62,7 +87,7 @@
docuteGoogleAnalytics('UA-13034748-12'), docuteGoogleAnalytics('UA-13034748-12'),
docuteApiTitlePlugin docuteApiTitlePlugin
], ],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.24.0/docs', sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.2/docs',
nav: [ nav: [
{ {
title: 'Funding', title: 'Funding',

View File

@@ -10,16 +10,17 @@ yarn add sharp
## Prerequisites ## Prerequisites
* Node.js v10.13.0+ * Node.js v10+
## Prebuilt binaries ## Prebuilt binaries
Ready-compiled sharp and libvips binaries are provided for use with Ready-compiled sharp and libvips binaries are provided for use with
Node.js versions 10, 12 and 13 on the most common platforms: Node.js v10+ on the most common platforms:
* macOS x64 (>= 10.13) * macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24) * Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Windows x64 with 64-bit `node.exe` * Linux ARM64 (glibc >= 2.29)
* Windows
A ~10MB tarball containing libvips and its most commonly used dependencies 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`. is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
@@ -30,8 +31,7 @@ JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp: The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6 * Linux ARMv6
* Linux ARMv7 * Linux ARMv7 (glibc >= 2.28)
* Linux ARM64 (glibc >= 2.29)
The following platforms require compilation of both libvips and sharp from source: The following platforms require compilation of both libvips and sharp from source:
@@ -42,18 +42,15 @@ The following platforms require compilation of both libvips and sharp from sourc
* FreeBSD * FreeBSD
* OpenBSD * OpenBSD
The following platforms are completely unsupported:
* Windows x86
* Windows x64 with 32-bit `node.exe`
## Common problems ## Common problems
The platform and major version of Node.js used for `npm install` The architecture and platform of Node.js used for `npm install`
must be the same as the platform and major version of Node.js used at runtime. 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 --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. Check the output of running `npm install --verbose sharp` for useful error messages.
## Custom libvips ## Custom libvips
@@ -77,8 +74,8 @@ This module will be compiled from source at `npm install` time when:
Building from source requires: Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7) * [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
## Custom prebuilt binaries ## Custom prebuilt binaries
@@ -127,24 +124,24 @@ to `false` when using the `yarn` package manager.
## AWS Lambda ## 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 The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform. must be for the Linux x64 platform.
On machines other than Linux x64, such as macOS and Windows, run the following: On machines other than Linux x64, such as macOS and Windows, run the following:
```sh ```sh
rm -rf node_modules/sharp 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: Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh ```sh
rm -rf node_modules/sharp 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. To get the best performance select the largest memory available.

2
docs/robots.txt Normal file
View File

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

View File

@@ -14,8 +14,14 @@ const agent = require('../lib/agent');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const platform = require('../lib/platform'); const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion; const minimumGlibcVersionByArch = {
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}/`; 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) { const fail = function (err) {
npmLog.error('sharp', err.message); npmLog.error('sharp', err.message);
@@ -57,18 +63,14 @@ try {
// Is this arch/platform supported? // Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch; const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform(); const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') { if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') { if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) { if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
const minimumGlibcVersion = arch === 'arm64' ? '2.29.0' : '2.17.0'; if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
if (semver.lt(`${detectLibc.version}.0`, minimumGlibcVersion)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
} }

View File

@@ -54,9 +54,10 @@ function ensureAlpha () {
* @example * @example
* sharp(input) * sharp(input)
* .extractChannel('green') * .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) { * .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // info.channels === 1 * // 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.

View File

@@ -100,8 +100,7 @@ function composite (images) {
if (!is.object(image)) { if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image); throw is.invalidParameterError('image to composite', 'object', image);
} }
const { raw, density } = image; const inputOptions = this._inputOptionsFromObject(image);
const inputOptions = (raw || density) ? { raw, density } : undefined;
const composite = { const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }), input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over', blend: 'over',

View File

@@ -16,6 +16,8 @@ try {
help.push('- Ensure the version of Node.js used at install time matches that used at runtime'); 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)) { } else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`); 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)) { } else if (/Cannot find module/.test(err.message)) {
help.push('- Run "npm rebuild --verbose sharp" and look for errors'); help.push('- Run "npm rebuild --verbose sharp" and look for errors');
} else { } else {
@@ -161,7 +163,7 @@ const Sharp = function (input, options) {
gamma: 0, gamma: 0,
gammaOut: 0, gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: false,
brightness: 1, brightness: 1,
saturation: 1, saturation: 1,
hue: 0, hue: 0,
@@ -217,6 +219,11 @@ const Sharp = function (input, options) {
heifCompression: 'hevc', heifCompression: 'hevc',
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
linearA: 1, linearA: 1,

View File

@@ -1,10 +1,20 @@
'use strict'; 'use strict';
const util = require('util');
const color = require('color'); const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); 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. * Create Object containing input and input-related options.
* @private * @private
@@ -24,12 +34,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.plainObject(input) && !is.defined(inputOptions)) { } else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create // Plain Object descriptor, e.g. create
inputOptions = input; inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) { if (_inputOptionsFromObject(inputOptions)) {
// Raw Stream // Stream with options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} }
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream without options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} else { } else {
throw new Error(`Unsupported input '${input}' of type ${typeof input}${ throw new Error(`Unsupported input '${input}' of type ${typeof input}${
@@ -187,9 +197,9 @@ function _isStreamInput () {
* - `size`: Total size of image in bytes, for Stream and Buffer input only * - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration) * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (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 * - `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 * - `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 * - `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 * - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
@@ -278,7 +288,7 @@ function metadata (callback) {
* - `minY` (y-coordinate of one of the pixel where the minimum lies) * - `minY` (y-coordinate of one of the pixel where the minimum lies)
* - `maxX` (x-coordinate of one of the pixel where the maximum 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) * - `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) * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* *
* @example * @example
@@ -331,32 +341,6 @@ function stats (callback) {
} }
} }
/**
* @private
*/
const limitInputPixels = util.deprecate(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.input.limitInputPixels = limit;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer', limit);
}
return this;
}, 'limitInputPixels is deprecated, use sharp(input, { limitInputPixels: false }) instead');
/**
* @private
*/
const sequentialRead = util.deprecate(function sequentialRead (sequentialRead) {
this.options.input.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this;
}, 'sequentialRead is deprecated, use sharp(input, { sequentialRead: true }) instead');
/** /**
* Decorate the Sharp prototype with input-related functions. * Decorate the Sharp prototype with input-related functions.
* @private * @private
@@ -364,15 +348,13 @@ const sequentialRead = util.deprecate(function sequentialRead (sequentialRead) {
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Private // Private
_inputOptionsFromObject,
_createInputDescriptor, _createInputDescriptor,
_write, _write,
_flattenBufferIn, _flattenBufferIn,
_isStreamInput, _isStreamInput,
// Public // Public
metadata, metadata,
stats, stats
// Deprecated
limitInputPixels,
sequentialRead
}); });
}; };

View File

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

View File

@@ -517,7 +517,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 * @example
* // Extract raw RGB pixel data from JPEG input * // Extract raw RGB pixel data from JPEG input
@@ -525,6 +527,15 @@ function heif (options) {
* .raw() * .raw()
* .toBuffer({ resolveWithObject: true }); * .toBuffer({ resolveWithObject: true });
* *
* @example
* // Extract alpha channel as raw pixel data from PNG input
* const data = await sharp('input.png')
* .ensureAlpha()
* .extractChannel(3)
* .colourspace('b-w')
* .raw()
* .toBuffer();
*
* @returns {Sharp} * @returns {Sharp}
*/ */
function raw () { function raw () {
@@ -557,7 +568,7 @@ function raw () {
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. * @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 {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). * @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -592,10 +603,10 @@ function tile (options) {
} }
// Layout // Layout
if (is.defined(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; this.options.tileLayout = options.layout;
} else { } 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, // Angle of rotation,

View File

@@ -85,21 +85,30 @@ const mapFitToCanvas = {
outside: 'min' 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`. * 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: * 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). * - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Embed within both provided dimensions. * - `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. * - `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. * - `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. * - `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. * 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: * When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. * - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`. * - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy. * - `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. * 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 * The experimental strategy-based approach resizes so one dimension is at its target length
@@ -367,7 +376,7 @@ function extract (options) {
} }
}, this); }, this);
// Ensure existing rotation occurs before pre-resize extraction // 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; this.options.rotateBeforePreExtract = true;
} }
return this; return this;
@@ -391,6 +400,9 @@ function trim (threshold) {
} else { } else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold); throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
} }
if (this.options.trimThreshold && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this; return this;
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.24.0", "version": "0.25.2",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -65,10 +65,11 @@
"Andargor <andargor@yahoo.com>", "Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>", "Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>", "Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>" "Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install --runtime=napi) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci", "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-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
@@ -109,40 +110,45 @@
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.2",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "node-addon-api": "^2.0.0",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.3", "prebuild-install": "^5.3.3",
"semver": "^7.1.1", "semver": "^7.1.3",
"simple-get": "^3.1.0", "simple-get": "^3.1.0",
"tar": "^5.0.5", "tar": "^6.0.1",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.0", "async": "^3.2.0",
"cc": "^2.0.1", "cc": "^2.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.2",
"documentation": "^12.1.4", "documentation": "^12.1.4",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^1.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^7.0.0", "mocha": "^7.1.1",
"mock-fs": "^4.10.4", "mock-fs": "^4.11.0",
"nyc": "^15.0.0", "nyc": "^15.0.0",
"prebuild": "^10.0.0", "prebuild": "^10.0.0",
"prebuild-ci": "^3.1.0", "prebuild-ci": "^3.1.0",
"rimraf": "^3.0.0", "rimraf": "^3.0.2",
"semistandard": "^14.2.0" "semistandard": "^14.2.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.9.0" "libvips": "8.9.1"
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
3
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@@ -19,9 +19,7 @@
#include <queue> #include <queue>
#include <mutex> // NOLINT(build/c++11) #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <napi.h>
#include <node_buffer.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -30,66 +28,77 @@ using vips::VImage;
namespace sharp { namespace sharp {
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) { bool HasAttr(Napi::Object obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); return obj.Has(attr);
} }
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) { std::string AttrAsStr(Napi::Object obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); return obj.Get(attr).As<Napi::String>();
} }
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) { uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr); return obj.Get(attr).As<Napi::Number>().Uint32Value();
std::vector<double> rgba(4); }
for (unsigned int i = 0; i < 4; i++) { int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
rgba[i] = AttrTo<double>(background, i); 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; return rgba;
} }
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input) {
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = buffer.Length();
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE; descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input // Density for vector-based input
if (HasAttr(input, "density")) { if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density"); descriptor->density = AttrAsDouble(input, "density");
} }
// Raw pixel input // Raw pixel input
if (HasAttr(input, "rawChannels")) { if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels"); descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages"); descriptor->pages = AttrAsInt32(input, "pages");
} }
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page"); descriptor->page = AttrAsUint32(input, "page");
} }
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground"); descriptor->createBackground = AttrAsRgba(input, "createBackground");
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrTo<uint32_t>(input, "limitInputPixels"); descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access // Allow switch from random to sequential access
descriptor->access = AttrTo<bool>(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
return descriptor; return descriptor;
} }
@@ -439,11 +448,9 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint) { std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
if (data != nullptr) { g_free(data);
g_free(data); };
}
}
/* /*
Temporary buffer of warnings Temporary buffer of warnings

View File

@@ -19,14 +19,13 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9))
#error "libvips version 8.9.0+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.9.1+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -82,23 +81,18 @@ namespace sharp {
createBackground{ 0.0, 0.0, 0.0, 255.0 } {} createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
}; };
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr); bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr); std::string AttrAsStr(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr); uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) { int32_t AttrAsInt32(Napi::Object obj, std::string attr);
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>(); double AttrAsDouble(Napi::Object obj, std::string attr);
} double AttrAsDouble(Napi::Object obj, unsigned int const attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) { bool AttrAsBool(Napi::Object obj, std::string attr);
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input);
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -211,7 +205,7 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows 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 Called with warnings from the glib-registered "VIPS" domain

View File

@@ -15,28 +15,16 @@
#include <numeric> #include <numeric>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Napi::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(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() {} ~MetadataWorker() {}
void Execute() { void Execute() {
@@ -137,140 +125,115 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
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->loop >= 0) {
Set(info, New("loop").ToLocalChecked(), New<v8::Uint32>(baton->loop));
}
if (!baton->delay.empty()) {
int i = 0;
v8::Local<v8::Array> delay = New<v8::Array>(baton->delay.size());
for (int const d : baton->delay) {
Set(delay, i++, New<v8::Number>(d));
}
Set(info, New("delay").ToLocalChecked(), delay);
}
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;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); 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: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { Napi::Value metadata(const Napi::CallbackInfo& info) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // 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 // 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 // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist)); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -16,7 +16,7 @@
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -81,6 +81,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_ #endif // SRC_METADATA_H_

View File

@@ -25,8 +25,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <vips/vips8> #include <vips/vips8>
#include <node.h> #include <napi.h>
#include <nan.h>
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
@@ -46,28 +45,18 @@
#define STAT64_FUNCTION stat64 #define STAT64_FUNCTION stat64
#endif #endif
class PipelineWorker : public Nan::AsyncWorker { class PipelineWorker : public Napi::AsyncWorker {
public: public:
PipelineWorker( PipelineWorker(Napi::Function callback, PipelineBaton *baton,
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener, Napi::Function debuglog, Napi::Function queueListener) :
std::vector<v8::Local<v8::Object>> const buffersToPersist) : Napi::AsyncWorker(callback),
Nan::AsyncWorker(callback, "sharp:PipelineWorker"), baton(baton),
baton(baton), debuglog(debuglog), queueListener(queueListener), debuglog(Napi::Persistent(debuglog)),
buffersToPersist(buffersToPersist) { queueListener(Napi::Persistent(queueListener)) {}
// 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() {} ~PipelineWorker() {}
// libuv worker // libuv worker
void Execute() { void Execute() {
using sharp::HasAlpha;
using sharp::ImageType;
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
// Increment processing task counter // Increment processing task counter
@@ -76,7 +65,7 @@ class PipelineWorker : public Nan::AsyncWorker {
try { try {
// Open input // Open input
vips::VImage image; vips::VImage image;
ImageType inputImageType; sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input); std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
// Calculate angle of rotation // Calculate angle of rotation
@@ -236,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
if ( if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor && 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 baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
) { ) {
if (xshrink >= 8 * shrink_on_load_factor) { if (xshrink >= 8 * shrink_on_load_factor) {
@@ -267,7 +256,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("fail", baton->input->failOnError); ->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer // Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option); image = VImage::jpegload_buffer(blob, option);
} else { } else {
@@ -276,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
vips_area_unref(reinterpret_cast<VipsArea*>(blob)); vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else { } else {
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file // Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} else { } else {
@@ -320,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Flatten image to remove alpha channel // 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 // Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour // Background colour
@@ -356,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldComposite = !baton->composite.empty(); bool const shouldComposite = !baton->composite.empty();
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0; 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); image = sharp::EnsureAlpha(image);
} }
bool const shouldPremultiplyAlpha = HasAlpha(image) && bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite); (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
// Premultiply image alpha channel before all transformations to avoid // Premultiply image alpha channel before all transformations to avoid
@@ -416,7 +405,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Join additional color channels to the image // Join additional color channels to the image
if (baton->joinChannelIn.size() > 0) { if (baton->joinChannelIn.size() > 0) {
VImage joinImage; VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN; sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
@@ -548,7 +537,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shouldComposite) { if (shouldComposite) {
for (Composite *composite : baton->composite) { for (Composite *composite : baton->composite) {
VImage compositeImage; VImage compositeImage;
ImageType compositeImageType = ImageType::UNKNOWN; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input); std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
// Verify within current dimensions // Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) { if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
@@ -584,7 +573,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Ensure image to composite is sRGB with premultiplied alpha // Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!HasAlpha(compositeImage)) { if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage); compositeImage = sharp::EnsureAlpha(compositeImage);
} }
if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
@@ -638,7 +627,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Apply bitwise boolean operation between images // Apply bitwise boolean operation between images
if (baton->boolean != nullptr) { if (baton->boolean != nullptr) {
VImage booleanImage; VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN; sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::Boolean(image, booleanImage, baton->booleanOp);
} }
@@ -703,9 +692,9 @@ class PipelineWorker : public Nan::AsyncWorker {
// Output // Output
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // 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 // Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@@ -727,9 +716,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && } 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 // Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@@ -744,9 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "png"; 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 // Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@@ -760,10 +751,12 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "webp"; 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 // Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { 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 // Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) { if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -786,8 +779,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "tiff"; baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3); } else if (baton->formatOut == "heif" ||
} else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == ImageType::HEIF)) { (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer // Write HEIF to buffer
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@@ -799,7 +792,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "heif"; 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 // Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
@@ -840,9 +834,9 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const mightMatchInput = baton->formatOut == "input"; bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == ImageType::JPEG)) { (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to file // 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() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@@ -856,9 +850,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && } 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 // 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() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@@ -870,9 +865,9 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("dither", baton->pngDither)); ->set("dither", baton->pngDither));
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
(willMatchInput && inputImageType == ImageType::WEBP)) { (willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to file // 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() image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@@ -883,10 +878,11 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == ImageType::TIFF)) { (willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { 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() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@@ -901,9 +897,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("xres", baton->tiffXres) ->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres)); ->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == ImageType::HEIF)) { (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file // Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) { if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
@@ -938,9 +933,6 @@ class PipelineWorker : public Nan::AsyncWorker {
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
} else { } 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 { std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->jpegQuality)}, {"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"}, {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
@@ -951,10 +943,11 @@ class PipelineWorker : public Nan::AsyncWorker {
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"}, {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"} {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
}; };
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options); suffix = AssembleSuffixString(extname, options);
} }
// Remove alpha channel from tile background if image does not contain an alpha channel // 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(); baton->tileBackground.pop_back();
} }
// Write DZ to file // Write DZ to file
@@ -976,7 +969,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image.dzsave(const_cast<char*>(baton->fileOut.data()), options); image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz"; baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) || } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) { (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
// Write V to file // Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)); ->set("strip", !baton->withMetadata));
@@ -1000,16 +993,18 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() }; // Handle warnings
if (!baton->err.empty()) { std::string warning = sharp::VipsWarningPop();
// Error while (!warning.empty()) {
argv[0] = Nan::Error(baton->err.data()); debuglog.Call({ Napi::String::New(env, warning) });
} else { warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width; int width = baton->width;
int height = baton->height; int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
@@ -1021,50 +1016,40 @@ class PipelineWorker : public Nan::AsyncWorker {
height = baton->heightPost; height = baton->heightPost;
} }
// Info Object // Info Object
v8::Local<v8::Object> info = New<v8::Object>(); Napi::Object info = Napi::Object::New(env);
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked()); info.Set("format", baton->formatOut);
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width))); info.Set("width", static_cast<uint32_t>(width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height))); info.Set("height", static_cast<uint32_t>(height));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels))); info.Set("channels", static_cast<uint32_t>(baton->channels));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied)); info.Set("premultiplied", baton->premultiplied);
if (baton->hasCropOffset) { if (baton->hasCropOffset) {
Set(info, New("cropOffsetLeft").ToLocalChecked(), info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetLeft))); info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
Set(info, New("cropOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetTop)));
} }
if (baton->trimThreshold > 0.0) { if (baton->trimThreshold > 0.0) {
Set(info, New("trimOffsetLeft").ToLocalChecked(), info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetLeft))); info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
Set(info, New("trimOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetTop)));
} }
if (baton->bufferOutLength > 0) { 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 // Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength))); info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
argv[2] = info; // 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 { } else {
// Add file size to info // Add file size to info
struct STAT64_STRUCT st; struct STAT64_STRUCT st;
if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) { 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
delete baton->input; delete baton->input;
delete baton->boolean; delete baton->boolean;
@@ -1077,29 +1062,16 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
delete baton; 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 // Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess); g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
queueListener->Call(1, queueLength, async_resource); queueListener.Call(Receiver().Value(), { queueLength });
delete queueListener;
// Return to JavaScript
callback->Call(3, argv, async_resource);
} }
private: private:
PipelineBaton *baton; PipelineBaton *baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
Nan::Callback *queueListener; Napi::FunctionReference queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
/* /*
Calculate the angle of rotation and need-to-flip for the given Exif orientation Calculate the angle of rotation and need-to-flip for the given Exif orientation
@@ -1169,37 +1141,27 @@ class PipelineWorker : public Nan::AsyncWorker {
/* /*
pipeline(options, output, callback) pipeline(options, output, callback)
*/ */
NAN_METHOD(pipeline) { Napi::Value pipeline(const Napi::CallbackInfo& info) {
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;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
PipelineBaton *baton = new PipelineBaton; PipelineBaton *baton = new PipelineBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Extract image options // Extract image options
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre"); baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre"); baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
baton->widthPre = AttrTo<int32_t>(options, "widthPre"); baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
baton->heightPre = AttrTo<int32_t>(options, "heightPre"); baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost"); baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost"); baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
baton->widthPost = AttrTo<int32_t>(options, "widthPost"); baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
baton->heightPost = AttrTo<int32_t>(options, "heightPost"); baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
// Output image dimensions // Output image dimensions
baton->width = AttrTo<int32_t>(options, "width"); baton->width = sharp::AttrAsInt32(options, "width");
baton->height = AttrTo<int32_t>(options, "height"); baton->height = sharp::AttrAsInt32(options, "height");
// Canvas option // Canvas option
std::string canvas = AttrAsStr(options, "canvas"); std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") { if (canvas == "crop") {
baton->canvas = Canvas::CROP; baton->canvas = Canvas::CROP;
} else if (canvas == "embed") { } else if (canvas == "embed") {
@@ -1212,191 +1174,168 @@ NAN_METHOD(pipeline) {
baton->canvas = Canvas::IGNORE_ASPECT; baton->canvas = Canvas::IGNORE_ASPECT;
} }
// Tint chroma // Tint chroma
baton->tintA = AttrTo<double>(options, "tintA"); baton->tintA = sharp::AttrAsDouble(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB"); baton->tintB = sharp::AttrAsDouble(options, "tintB");
// Composite // Composite
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked()) Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
.ToLocalChecked().As<v8::Array>(); for (unsigned int i = 0; i < compositeArray.Length(); i++) {
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length"); Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
for (int i = 0; i < compositeArrayLength; i++) {
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
Composite *composite = new Composite; 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>( composite->mode = static_cast<VipsBlendMode>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data())); vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity"); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = AttrTo<int32_t>(compositeObject, "left"); composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top"); composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile"); composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite); baton->composite.push_back(composite);
} }
// Resize options // Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement"); baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = AttrTo<int32_t>(options, "position"); baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = AttrAsRgba(options, "resizeBackground"); baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
baton->kernel = AttrAsStr(options, "kernel"); baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad"); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options // Join Channel Options
if (HasAttr(options, "joinChannelIn")) { if (options.Has("joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked()) Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
.ToLocalChecked().As<v8::Object>(); for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for (int i = 0; i < joinChannelArrayLength; i++) {
baton->joinChannelIn.push_back( baton->joinChannelIn.push_back(
CreateInputDescriptor( sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
buffersToPersist));
} }
} }
// Operators // Operators
baton->flatten = AttrTo<bool>(options, "flatten"); baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = AttrAsRgba(options, "flattenBackground"); baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation"); baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = AttrTo<int32_t>(options, "hue"); baton->hue = sharp::AttrAsInt32(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize"); baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma"); baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat"); baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged"); baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold"); baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale"); baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma"); baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA"); baton->linearA = sharp::AttrAsDouble(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB"); baton->linearB = sharp::AttrAsDouble(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale"); baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise"); baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation"); baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle"); baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = AttrTo<double>(options, "rotationAngle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = AttrAsRgba(options, "rotationBackground"); baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip"); baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = AttrTo<bool>(options, "flop"); baton->flop = sharp::AttrAsBool(options, "flop");
baton->extendTop = AttrTo<int32_t>(options, "extendTop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = AttrAsRgba(options, "extendBackground"); baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel"); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha"); baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
if (HasAttr(options, "boolean")) { if (options.Has("boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist); baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp")); baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
} }
if (HasAttr(options, "bandBoolOp")) { if (options.Has("bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp")); baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
} }
if (HasAttr(options, "convKernel")) { if (options.Has("convKernel")) {
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel"); Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width"); baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height"); baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
baton->convKernelScale = AttrTo<double>(kernel, "scale"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = AttrTo<double>(kernel, "offset"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight); size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]); 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++) { 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]); 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++) { 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) { if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
baton->colourspace = VIPS_INTERPRETATION_sRGB; baton->colourspace = VIPS_INTERPRETATION_sRGB;
} }
// Output // Output
baton->formatOut = AttrAsStr(options, "formatOut"); baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = AttrTo<bool>(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
// Format-specific // Format-specific
baton->jpegQuality = AttrTo<uint32_t>(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation"); baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable"); baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing"); baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans"); baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding"); baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive"); baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel"); baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering"); baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = AttrTo<bool>(options, "pngPalette"); baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality"); baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngColours = AttrTo<uint32_t>(options, "pngColours"); baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
baton->pngDither = AttrTo<double>(options, "pngDither"); baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality"); baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality"); baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample"); baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort"); baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash"); baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
// tiff compression options // tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>( baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data())); sharp::AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>( baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data())); sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality"); baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = AttrTo<bool>(options, "heifLossless"); baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>( baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
AttrAsStr(options, "heifCompression").data())); sharp::AttrAsStr(options, "heifCompression").data()));
// Tile output // Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize"); baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle"); baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
baton->tileBackground = AttrAsRgba(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks"); baton->tileContainer = static_cast<VipsForeignDzContainer>(
if (tileContainer == "zip") { vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; sharp::AttrAsStr(options, "tileContainer").data()));
} else { baton->tileLayout = static_cast<VipsForeignDzLayout>(
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS; vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
} sharp::AttrAsStr(options, "tileLayout").data()));
std::string tileLayout = AttrAsStr(options, "tileLayout"); baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
if (tileLayout == "google") { baton->tileDepth = static_cast<VipsForeignDzDepth>(
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE; vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
} else if (tileLayout == "zoomify") { sharp::AttrAsStr(options, "tileDepth").data()));
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;
}
// Force random access for certain operations // Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) { if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
@@ -1404,25 +1343,30 @@ NAN_METHOD(pipeline) {
baton->trimThreshold > 0.0 || baton->trimThreshold > 0.0 ||
baton->normalise || baton->normalise ||
baton->position == 16 || baton->position == 17 || baton->position == 16 || baton->position == 17 ||
baton->angle != 0 || baton->rotationAngle != 0.0 baton->angle % 360 != 0 ||
fmod(baton->rotationAngle, 360.0) != 0.0 ||
baton->useExifOrientation
) { ) {
baton->input->access = VIPS_ACCESS_RANDOM; baton->input->access = VIPS_ACCESS_RANDOM;
} }
} }
// Function to notify of libvips warnings // 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 // 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 // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist)); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
v8::Local<v8::Object> recv = Nan::New<v8::Object>(); queueListener.Call(info.This(), { queueLength });
Nan::Call(*queueListener, recv, 1, queueLength);
return info.Env().Undefined();
} }

View File

@@ -19,12 +19,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <nan.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "./common.h" #include "./common.h"
NAN_METHOD(pipeline); Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas { enum class Canvas {
CROP, CROP,
@@ -150,7 +150,7 @@ struct PipelineBaton {
double tiffXres; double tiffXres;
double tiffYres; double tiffYres;
int heifQuality; int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression VipsForeignHeifCompression heifCompression;
bool heifLossless; bool heifLossless;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
@@ -258,7 +258,7 @@ struct PipelineBaton {
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
heifQuality(80), heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifLossless(false), heifLossless(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),

View File

@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -22,33 +21,24 @@
#include "utilities.h" #include "utilities.h"
#include "stats.h" #include "stats.h"
NAN_MODULE_INIT(init) { Napi::Object init(Napi::Env env, Napi::Object exports) {
vips_init("sharp"); vips_init("sharp");
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING), g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), exports.Set("metadata", Napi::Function::New(env, metadata));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); exports.Set("pipeline", Napi::Function::New(env, pipeline));
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(), exports.Set("cache", Napi::Function::New(env, cache));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked()); exports.Set("concurrency", Napi::Function::New(env, concurrency));
Nan::Set(target, Nan::New("cache").ToLocalChecked(), exports.Set("counters", Napi::Function::New(env, counters));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked()); exports.Set("simd", Napi::Function::New(env, simd));
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(), exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked()); exports.Set("format", Napi::Function::New(env, format));
Nan::Set(target, Nan::New("counters").ToLocalChecked(), exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked()); exports.Set("stats", Napi::Function::New(env, stats));
Nan::Set(target, Nan::New("simd").ToLocalChecked(), return exports;
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());
} }
NAN_MODULE_WORKER_ENABLED(sharp, init) NODE_API_MODULE(sharp, init)

View File

@@ -16,28 +16,16 @@
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "stats.h" #include "stats.h"
class StatsWorker : public Nan::AsyncWorker { class StatsWorker : public Napi::AsyncWorker {
public: public:
StatsWorker( StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(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() {} ~StatsWorker() {}
const int STAT_MIN_INDEX = 0; const int STAT_MIN_INDEX = 0;
@@ -54,13 +42,9 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image; vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
@@ -71,20 +55,23 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats(); vips::VImage stats = image.stats();
int const bands = image.bands(); int const bands = image.bands();
for (int b = 1; b <= bands; b++) { for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()), ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()), static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(), stats.getpoint(STAT_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()), stats.getpoint(STAT_MEAN_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXY_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); baton->channelStats.push_back(cStats);
} }
// Image is not opaque when alpha layer is present and contains a non-mamixa value // Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) { if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) { if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false; baton->isOpaque = false;
} }
} }
@@ -100,92 +87,78 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
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;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); // 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: private:
StatsBaton* baton; StatsBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
stats(options, callback) stats(options, callback)
*/ */
NAN_METHOD(stats) { Napi::Value stats(const Napi::CallbackInfo& info) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton; StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // 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 // 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 // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist)); StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -16,7 +16,7 @@
#define SRC_STATS_H_ #define SRC_STATS_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -33,12 +33,8 @@ struct ChannelStats {
int maxX; int maxX;
int maxY; int maxY;
ChannelStats(): ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0) double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
, 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):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
}; };
@@ -61,6 +57,6 @@ struct StatsBaton {
{} {}
}; };
NAN_METHOD(stats); Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_ #endif // SRC_STATS_H_

View File

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

View File

@@ -15,14 +15,14 @@
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_
#include <nan.h> #include <napi.h>
NAN_METHOD(cache); Napi::Value cache(const Napi::CallbackInfo& info);
NAN_METHOD(concurrency); Napi::Value concurrency(const Napi::CallbackInfo& info);
NAN_METHOD(counters); Napi::Value counters(const Napi::CallbackInfo& info);
NAN_METHOD(simd); Napi::Value simd(const Napi::CallbackInfo& info);
NAN_METHOD(libvipsVersion); Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
NAN_METHOD(format); Napi::Value format(const Napi::CallbackInfo& info);
NAN_METHOD(_maxColourDistance); Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_ #endif // SRC_UTILITIES_H_

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

@@ -578,11 +578,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei fun:_ZN4node20BackgroundTaskRunnerC1Ei
} }
{ {
leak_nan_FunctionCallbackInfo leak_napi_module_register
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite 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 leak_v8_FunctionCallbackInfo

View File

@@ -5,7 +5,10 @@ const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL; const usingCache = detectLibc.family !== detectLibc.MUSL;
const usingSimd = !process.env.G_DEBUG; 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 () { beforeEach(function () {
sharp.cache(usingCache); sharp.cache(usingCache);

View File

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

View File

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

View File

@@ -234,22 +234,6 @@ describe('Input/output', function () {
}) })
); );
it('Sequential read, force JPEG - deprecated', 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('Not sequential read, force JPEG', () => it('Not sequential read, force JPEG', () =>
sharp(fixtures.inputJpg, { sequentialRead: false }) sharp(fixtures.inputJpg, { sequentialRead: false })
.resize(320, 240) .resize(320, 240)
@@ -264,22 +248,6 @@ describe('Input/output', function () {
}) })
); );
it('Not sequential read, force JPEG - deprecated', function (done) {
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);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Support output to jpg format', function (done) { it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.resize(320, 240) .resize(320, 240)
@@ -653,81 +621,6 @@ describe('Input/output', function () {
); );
}); });
describe('Limit pixel count of input image - deprecated', 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();
});
});
});
it('Disabling limit works', function (done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(false)
.resize(2)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
});
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('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();
});
});
});
});
describe('Input options', function () { describe('Input options', function () {
it('Option-less', function () { it('Option-less', function () {
sharp(); sharp();

View File

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

View File

@@ -168,5 +168,19 @@ describe('Raw pixel data', function () {
done(); 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

@@ -208,11 +208,48 @@ describe('TIFF', function () {
.toFile(fixtures.outputTiff, (err, info) => { .toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', info.format); assert.strictEqual('tiff', info.format);
assert.strictEqual(3, info.channels);
assert(info.size < startSize); assert(info.size < startSize);
rimraf(fixtures.outputTiff, done); 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) { it('TIFF ccittfax4 compression shrinks b-w test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiff).size; const startSize = fs.statSync(fixtures.inputTiff).size;
sharp(fixtures.inputTiff) 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) { it('Write to ZIP container using file extension', function (done) {
const container = fixtures.path('output.dz.container.zip'); const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container'); const extractTo = fixtures.path('output.dz.container');

View File

@@ -94,6 +94,32 @@ describe('Trim borders', function () {
.catch(done); .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 () { describe('Invalid thresholds', function () {
[-1, 'fail', {}].forEach(function (threshold) { [-1, 'fail', {}].forEach(function (threshold) {
it(JSON.stringify(threshold), function () { it(JSON.stringify(threshold), function () {