Compare commits

..

58 Commits

Author SHA1 Message Date
Lovell Fuller
596b38a3bb Release v0.23.4 2019-12-05 10:00:29 +00:00
Lovell Fuller
d31a91a599 Expose raw TIFFTAG_PHOTOSHOP metadata #1600 2019-11-29 13:05:07 +00:00
Lovell Fuller
400ef71b6f Handle zero-length Buffers in Node.js v13.2.0+
These now use nullptr internally - see
https://github.com/nodejs/node/pull/30339
2019-11-28 21:20:32 +00:00
Lovell Fuller
bb15cd9067 Improve thread safety with copy-on-write for metadata #1986 2019-11-27 23:15:56 +00:00
Lovell Fuller
6ee6a226e1 Docs: add FreeBSD build status 2019-11-19 11:16:48 +00:00
Lovell Fuller
94d51a94c8 Docs: recommend jemalloc buildpack for Heroku 2019-11-19 11:13:25 +00:00
Lovell Fuller
d0feb4156c Release v0.23.3 2019-11-17 15:42:07 +00:00
Lovell Fuller
0d1278dade Improve C++ linting, move exceptions inline 2019-11-14 22:06:38 +00:00
Lovell Fuller
1b401b1195 Add FreeBSD to CI via Cirrus #1953 2019-11-14 21:55:20 +00:00
Lovell Fuller
11daa3b4d1 Tests: flatten to mid-grey before generating fingerprint 2019-11-14 13:18:14 +00:00
Lovell Fuller
88a3919ce0 Docs refresh 2019-11-14 11:32:49 +00:00
Lovell Fuller
c41b87303d Ensure trim op supports image-in-alpha #1597 2019-11-14 11:29:45 +00:00
Lovell Fuller
833aaead56 Ensure modulate can co-exist with other colour ops #1958 2019-11-11 22:16:28 +00:00
Lovell Fuller
ff98d2e44a Docs: using custom binaries with prebuild-install 2019-11-07 20:00:03 +00:00
Lovell Fuller
fcf05f608a Changelog entry for #1952 2019-11-07 19:59:18 +00:00
Pouya Eghbali
9baf38db44 Allow compilation of v0.23.x on FreeBSD and variants (#1952) 2019-11-06 23:11:44 +00:00
Lovell Fuller
69050ef1c8 Add funding link to libvips' Open Collective 2019-11-05 21:51:14 +00:00
Lovell Fuller
b35b9f7850 Ensure Travis does not cache builds #1948 2019-11-01 16:17:36 +00:00
Lovell Fuller
500ae97cac Changelog entry for #1921 2019-11-01 16:15:02 +00:00
Brendan Kennedy
d5b7040557 Ensure tile overlap option works as expected (#1921) 2019-10-30 20:02:07 +00:00
Lovell Fuller
ca52894651 Release v0.23.2 2019-10-28 18:21:11 +00:00
Lovell Fuller
4009acdd30 Docs: support added for Node.js 13 2019-10-28 18:19:33 +00:00
Lovell Fuller
147c93ecd3 Tests: increase coverage for jpeg-related logic 2019-10-27 20:03:10 +00:00
Lovell Fuller
8e04e4b07f Tests: add coverage for tiff quality option 2019-10-27 19:54:23 +00:00
Lovell Fuller
e7413ea1e5 Tests: increase coverage for metadata-related logic 2019-10-27 19:32:48 +00:00
Lovell Fuller
220bb03a32 Switch from excl npmignore to incl package.json files 2019-10-27 18:22:10 +00:00
Lovell Fuller
20f512fe5f Bump dependency patch versions 2019-10-26 23:15:41 +01:00
Lovell Fuller
efb3523eaa Remove duplicate validation from resize background 2019-10-26 23:13:12 +01:00
Lovell Fuller
2f2276e091 Changelog entries for #1924 #1932 2019-10-26 22:53:39 +01:00
Paul Neave
08a6597626 Add background option to tile output operation (#1924) 2019-10-25 14:30:33 +01:00
Nicolas Stepien
d82a6ee4fc Add Node.js 13 to CI (#1932) 2019-10-23 19:28:19 +01:00
Lovell Fuller
e627f6d68d Docs: clarify that input 'path' refers to the filesystem 2019-10-05 08:43:40 +01:00
Lovell Fuller
e650f58bd8 Improve error messaging for root/sudo permission problems 2019-10-04 12:14:08 +01:00
Lovell Fuller
5a9b6c8afd Tighten validation of page/pages constructor options 2019-10-03 16:41:32 +01:00
Lovell Fuller
075771d1e9 Improve error messaging for 404 errors on non-standard platforms 2019-10-03 15:32:15 +01:00
Lovell Fuller
4fcf091fef Bump tar dep (appears to be non-breaking despite major increment) 2019-10-03 15:30:56 +01:00
Marc Bornträger
0e66454fe4 Docs: Simplify Alpine Linux info, uses 3.10 instead of edge 2019-10-01 12:00:38 +01:00
Lovell Fuller
aa3ce760bb Release v0.23.1 2019-09-26 10:19:06 +01:00
Lovell Fuller
ba46ad1fd9 Docs: mention removal of metadata in output methods 2019-09-25 16:56:52 +01:00
Lovell Fuller
11214bab5d Bump tar dependency to ensure minipass >=2.8.6 2019-09-25 14:31:54 +01:00
Lovell Fuller
d87c289b4a Update results from latest benchmark test run 2019-09-25 14:16:51 +01:00
Lovell Fuller
af45c03b6f Bump benchmark dependencies ahead of a perf test run 2019-09-25 12:03:16 +01:00
Lovell Fuller
7d6fadce6b Ensure promise-based benchmark test is fairer
Real-world code will register a catch handler
2019-09-25 12:01:43 +01:00
Lovell Fuller
6b560f7a85 Remove imagemagick-native module from benchmarks
Unmaintained, does not compile with newer ImageMagick and Node
2019-09-25 10:13:45 +01:00
Lovell Fuller
5d4221460d Docs: Add more prominent link to libvips 2019-09-24 10:53:33 +01:00
Lovell Fuller
d42c383992 Add link from GitHub to libvips OpenCollective 2019-09-24 10:49:52 +01:00
Lovell Fuller
9c7f6fcb2b Replace deprecated URL parser
Fix up various linter errors
2019-09-22 22:46:48 +01:00
Lovell Fuller
14af0bda61 Regenerate flatten-rgb16-orange test expectation, reduce threshold 2019-09-21 20:01:15 +01:00
Raboliot le gris
fb5c393fbd Allow instance reuse with differing toBuffer options (#1860) 2019-09-08 14:35:16 +01:00
Lovell Fuller
69fe21a7ec Ensure invalid resize width/height as options throw #1817 2019-08-16 21:21:12 +01:00
Lovell Fuller
da4e05c118 Better validation and test coverage for background colours 2019-08-16 20:37:17 +01:00
Lovell Fuller
e4333ff6b0 Changelog entry, credit and doc update for #1835 2019-08-14 20:17:31 +01:00
Andargor
4ae8999f62 Add premultiplied option to composite operation (#1835) 2019-08-14 19:01:23 +01:00
Lovell Fuller
3fa91bb4ce Ensure image >= 3x3 before attempting trim operation
See https://github.com/libvips/libvips/issues/1392
2019-08-13 21:34:49 +01:00
Lovell Fuller
23b2e541ab Changelog entry for #1834 2019-08-12 21:45:29 +01:00
Julian Aubourg
5bfcf61a6f Allow use of heic/heif identifiers with toFormat (#1834) 2019-08-12 21:36:56 +01:00
Lovell Fuller
0778c112a9 Ensure sharp.format.vips is present and correct #1813 2019-08-12 21:25:10 +01:00
Lovell Fuller
2c300754a7 Expand issue templates to direct towards installation 2019-07-31 16:48:32 +01:00
66 changed files with 1239 additions and 698 deletions

13
.cirrus.yml Normal file
View File

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

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
open_collective: libvips

View File

@@ -1,16 +1,18 @@
--- ---
name: Installation name: Installation
about: For help if something went wrong installing sharp about: Something went wrong **installing** sharp
title: '' title: ''
labels: '' labels: installation
assignees: '' assignees: ''
--- ---
What is the output of running `npm install --verbose sharp`? Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/install/)?
What is the output of running `npx envinfo --binaries --languages --system --utilities`?
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?
If you're using `sudo npm install` have you tried with the `sudo 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?
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`?

View File

@@ -1,12 +1,14 @@
--- ---
name: Possible bug name: Possible bug
about: Please provide steps to reproduce about: Something unexpected occurred **using** sharp
title: '' title: ''
labels: '' labels: triage
assignees: '' assignees: ''
--- ---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What is the output of running `npx envinfo --binaries --languages --system --utilities`? What is the output of running `npx envinfo --binaries --languages --system --utilities`?
What are the steps to reproduce? What are the steps to reproduce?

View File

@@ -1,12 +1,14 @@
--- ---
name: Question name: Question
about: For help with an existing feature about: For help understanding an existing feature
title: '' title: ''
labels: question labels: question
assignees: '' assignees: ''
--- ---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What are you trying to achieve? What are you trying to achieve?
Have you searched for similar questions? Have you searched for similar questions?

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ vendor
.nyc_output .nyc_output
.vscode/ .vscode/
package-lock.json package-lock.json
.idea

View File

@@ -1,16 +0,0 @@
build
node_modules
coverage
.editorconfig
.gitattributes
.gitignore
test
.travis.yml
appveyor.yml
mkdocs.yml
docs/css/
vendor
.prebuildrc
.nyc_output
.github/
.vscode/

View File

@@ -18,6 +18,12 @@ matrix:
sudo: false sudo: false
language: node_js language: node_js
node_js: "12" node_js: "12"
- name: "Linux (glibc) - Node 13"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "13"
after_success: after_success:
- npm install coveralls - npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
@@ -51,6 +57,16 @@ matrix:
- 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) - Node 13"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:13.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "OS X - Node 8" - name: "OS X - Node 8"
os: osx os: osx
osx_image: xcode9.2 osx_image: xcode9.2
@@ -66,3 +82,10 @@ matrix:
osx_image: xcode9.2 osx_image: xcode9.2
language: node_js language: node_js
node_js: "12" node_js: "12"
- name: "OS X - Node 13"
os: osx
osx_image: xcode10
language: node_js
node_js: "13"
cache:
npm: false

View File

@@ -11,7 +11,8 @@ is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings. quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed. Lanczos resampling ensures quality is not sacrificed for speed.
@@ -20,7 +21,7 @@ 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 OS X, Windows and Linux systems running Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12 Node versions 8, 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
## Examples ## Examples

View File

@@ -7,6 +7,7 @@ environment:
- nodejs_version: "8" - nodejs_version: "8"
- nodejs_version: "10" - nodejs_version: "10"
- nodejs_version: "12" - nodejs_version: "12"
- nodejs_version: "13"
install: install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64 - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
- npm install -g npm@6 - npm install -g npm@6

View File

@@ -183,16 +183,22 @@
}, },
'configurations': { 'configurations': {
'Release': { 'Release': {
'cflags_cc': [ 'conditions': [
'-Wno-cast-function-type' ['OS == "linux"', {
], 'cflags_cc': [
'msvs_settings': { '-Wno-cast-function-type'
'VCCLCompilerTool': { ]
'ExceptionHandling': 1 }],
} ['OS == "win"', {
}, 'msvs_settings': {
'msvs_disabled_warnings': [ 'VCCLCompilerTool': {
4275 'ExceptionHandling': 1
}
},
'msvs_disabled_warnings': [
4275
]
}]
] ]
} }
}, },

View File

@@ -31,6 +31,7 @@ and [https://www.cairographics.org/operators/][2]
- `images[].top` **[Number][7]?** the pixel offset from the top edge. - `images[].top` **[Number][7]?** the pixel offset from the top edge.
- `images[].left` **[Number][7]?** the pixel offset from the left edge. - `images[].left` **[Number][7]?** the pixel offset from the left edge.
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`) - `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`) - `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data. - `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
- `images[].raw.width` **[Number][7]?** - `images[].raw.width` **[Number][7]?**

View File

@@ -6,7 +6,7 @@
- `input` **([Buffer][1] \| [String][2])?** if present, can be - `input` **([Buffer][1] \| [String][2])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][3]?** if present, is an Object with optional attributes. - `options` **[Object][3]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images. - `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.

View File

@@ -44,6 +44,7 @@ A `Promise` is returned when `callback` is not provided.
- `icc`: Buffer containing raw [ICC][3] profile data, if present - `icc`: Buffer containing raw [ICC][3] profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present - `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present - `xmp`: Buffer containing raw XMP data, if present
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
### Parameters ### Parameters

View File

@@ -8,12 +8,15 @@ If an explicit output format is not selected, it will be inferred from the exten
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported. with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output. Note that raw pixel data is only supported for buffer output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [withMetadata][1] for control over this.
A `Promise` is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `fileOut` **[String][1]** the path to write the image data to. - `fileOut` **[String][2]** the path to write the image data to.
- `callback` **[Function][2]?** called on completion with two arguments `(err, info)`. - `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`, `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used). `channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
@@ -32,15 +35,19 @@ sharp(input)
.catch(err => { ... }); .catch(err => { ... });
``` ```
- Throws **[Error][3]** Invalid parameters - Throws **[Error][4]** Invalid parameters
Returns **[Promise][4]&lt;[Object][5]>** when no callback is provided Returns **[Promise][5]&lt;[Object][6]>** when no callback is provided
## toBuffer ## toBuffer
Write output to a Buffer. Write output to a Buffer.
JPEG, PNG, WebP, TIFF and RAW output are supported. JPEG, PNG, WebP, TIFF and RAW output are supported.
By default, the format will match the input image, except GIF and SVG input which become PNG output.
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [withMetadata][1] for control over this.
`callback`, if present, gets three arguments `(err, data, info)` where: `callback`, if present, gets three arguments `(err, data, info)` where:
@@ -54,9 +61,9 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `options` **[Object][5]?** - `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][6]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. - `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][2]?** - `callback` **[Function][3]?**
### Examples ### Examples
@@ -79,7 +86,7 @@ sharp(input)
.catch(err => { ... }); .catch(err => { ... });
``` ```
Returns **[Promise][4]&lt;[Buffer][7]>** when no callback is provided Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata ## withMetadata
@@ -89,8 +96,8 @@ This will also convert to and add a web-friendly sRGB ICC profile.
### Parameters ### Parameters
- `options` **[Object][5]?** - `options` **[Object][6]?**
- `options.orientation` **[Number][8]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
### Examples ### Examples
@@ -101,7 +108,7 @@ sharp('input.jpg')
.then(info => { ... }); .then(info => { ... });
``` ```
- Throws **[Error][3]** Invalid parameters - Throws **[Error][4]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -111,19 +118,19 @@ Use these JPEG options for output image.
### Parameters ### Parameters
- `options` **[Object][5]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][1]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][6]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean][6]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean][6]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`) - `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.optimiseCoding` **[Boolean][6]** optimise Huffman coding tables (optional, default `true`) - `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[Boolean][6]** alternative spelling of optimiseCoding (optional, default `true`) - `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[Number][8]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`) - `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][8]** alternative spelling of quantisationTable (optional, default `0`) - `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[Boolean][6]** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -137,7 +144,7 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][3]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -150,16 +157,16 @@ Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
### Parameters ### Parameters
- `options` **[Object][5]?** - `options` **[Object][6]?**
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][8]** zlib compression level, 0-9 (optional, default `9`) - `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][6]** use adaptive row filtering (optional, default `false`) - `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][6]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`) - `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[Number][8]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`) - `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][8]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][8]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[Number][8]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`) - `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.force` **[Boolean][6]** force PNG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -170,7 +177,7 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][3]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -180,14 +187,14 @@ Use these WebP options for output image.
### Parameters ### Parameters
- `options` **[Object][5]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number][8]** quality of alpha layer, integer 0-100 (optional, default `100`) - `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][6]** use lossless compression mode (optional, default `false`) - `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][6]** use near_lossless compression mode (optional, default `false`) - `options.nearLossless` **[Boolean][7]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[Boolean][6]** use high quality chroma subsampling (optional, default `false`) - `options.smartSubsample` **[Boolean][7]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[Number][8]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`) - `options.reductionEffort` **[Number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -198,7 +205,7 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][3]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -208,18 +215,18 @@ Use these TIFF options for output image.
### Parameters ### Parameters
- `options` **[Object][5]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean][6]** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][6]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`) - `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][6]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`) - `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][6]** write an image pyramid (optional, default `false`) - `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][6]** write a tiled tiff (optional, default `false`) - `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][6]** horizontal tile size (optional, default `256`) - `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][6]** vertical tile size (optional, default `256`) - `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][8]** horizontal resolution in pixels/mm (optional, default `1.0`) - `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][8]** vertical resolution in pixels/mm (optional, default `1.0`) - `options.yres` **[Number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][6]** squash 8-bit images down to 1 bit (optional, default `false`) - `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`)
### Examples ### Examples
@@ -234,7 +241,7 @@ sharp('input.svg')
.then(info => { ... }); .then(info => { ... });
``` ```
- Throws **[Error][3]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -251,13 +258,13 @@ Most versions of libheif support only the patent-encumbered HEVC compression for
### Parameters ### Parameters
- `options` **[Object][5]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][8]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[Boolean][6]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`) - `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[Boolean][6]** use lossless compression (optional, default `false`) - `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`)
- Throws **[Error][3]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -282,8 +289,8 @@ Force output to a given format.
### Parameters ### Parameters
- `format` **([String][1] \| [Object][5])** as a String or an Object with an 'id' attribute - `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute
- `options` **[Object][5]** output options - `options` **[Object][6]** output options
### Examples ### Examples
@@ -294,7 +301,7 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][3]** unsupported format or options - Throws **[Error][4]** unsupported format or options
Returns **Sharp** Returns **Sharp**
@@ -308,14 +315,15 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters ### Parameters
- `options` **[Object][5]?** - `options` **[Object][6]?**
- `options.size` **[Number][8]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) - `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`) - `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. - `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.skipBlanks` **[Number][8]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`) - `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `options.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `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.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) - `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'`)
### Examples ### Examples
@@ -331,22 +339,26 @@ sharp('input.tiff')
}); });
``` ```
- Throws **[Error][3]** Invalid parameters - Throws **[Error][4]** Invalid parameters
Returns **Sharp** Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String [1]: #withmetadata
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean [6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://nodejs.org/api/buffer.html [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [8]: https://nodejs.org/api/buffer.html
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[10]: https://www.npmjs.org/package/color

View File

@@ -196,6 +196,8 @@ Returns **Sharp**
## trim ## trim
Trim "boring" pixels from all edges that contain values similar to the top-left pixel. Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters ### Parameters

View File

@@ -4,6 +4,64 @@
Requires libvips v8.8.1. Requires libvips v8.8.1.
#### v0.23.4 - 5<sup>th</sup> December 2019
* Handle zero-length Buffer objects when using Node.js v13.2.0+.
* Expose raw TIFFTAG_PHOTOSHOP metadata.
[#1600](https://github.com/lovell/sharp/issues/1600)
* Improve thread safety by using copy-on-write when updating metadata.
[#1986](https://github.com/lovell/sharp/issues/1986)
#### v0.23.3 - 17<sup>th</sup> November 2019
* Ensure `trim` operation supports images contained in the alpha channel.
[#1597](https://github.com/lovell/sharp/issues/1597)
* Ensure tile `overlap` option works as expected.
[#1921](https://github.com/lovell/sharp/pull/1921)
[@rustyguts](https://github.com/rustyguts)
* Allow compilation on FreeBSD and variants (broken since v0.23.0)
[#1952](https://github.com/lovell/sharp/pull/1952)
[@pouya-eghbali](https://github.com/pouya-eghbali)
* Ensure `modulate` and other colour-based operations can co-exist.
[#1958](https://github.com/lovell/sharp/issues/1958)
#### v0.23.2 - 28<sup>th</sup> October 2019
* Add `background` option to tile output operation.
[#1924](https://github.com/lovell/sharp/pull/1924)
[@neave](https://github.com/neave)
* Add support for Node.js 13.
[#1932](https://github.com/lovell/sharp/pull/1932)
[@MayhemYDG](https://github.com/MayhemYDG)
#### v0.23.1 - 26<sup>th</sup> September 2019
* Ensure `sharp.format.vips` is present and correct (filesystem only).
[#1813](https://github.com/lovell/sharp/issues/1813)
* Ensure invalid `width` and `height` provided as options to `resize` throw.
[#1817](https://github.com/lovell/sharp/issues/1817)
* Allow use of 'heic' and 'heif' identifiers with `toFormat`.
[#1834](https://github.com/lovell/sharp/pull/1834)
[@jaubourg](https://github.com/jaubourg)
* Add `premultiplied` option to `composite` operation.
[#1835](https://github.com/lovell/sharp/pull/1835)
[@Andargor](https://github.com/Andargor)
* Allow instance reuse with differing `toBuffer` options.
[#1860](https://github.com/lovell/sharp/pull/1860)
[@RaboliotTheGrey](https://github.com/RaboliotTheGrey)
* Ensure image is at least 3x3 pixels before attempting trim operation.
#### v0.23.0 - 29<sup>th</sup> July 2019 #### v0.23.0 - 29<sup>th</sup> July 2019
* Remove `overlayWith` previously deprecated in v0.22.0. * Remove `overlayWith` previously deprecated in v0.22.0.

View File

@@ -7,7 +7,8 @@ is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings. quickest ImageMagick and GraphicsMagick settings
due to its use of [libvips](https://github.com/libvips/libvips).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Lanczos resampling ensures quality is not sacrificed for speed. Lanczos resampling ensures quality is not sacrificed for speed.
@@ -16,7 +17,7 @@ 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 OS X, Windows and Linux systems running Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12 Node versions 8, 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) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
@@ -126,6 +127,10 @@ the help and code contributions of the following people:
* [Michael B. Klein](https://github.com/mbklein) * [Michael B. Klein](https://github.com/mbklein)
* [Jakub Michálek](https://github.com/Goues) * [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin) * [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor)
* [Nicolas Stepien](https://github.com/MayhemYDG)
* [Paul Neave](https://github.com/neave)
* [Brendan Kennedy](https://github.com/rustyguts)
Thank you! Thank you!

View File

@@ -15,7 +15,7 @@ yarn add sharp
### Building from source ### Building from source
Pre-compiled binaries for sharp are provided for use with Pre-compiled binaries for sharp are provided for use with
Node versions 8, 10 and 12 on Node versions 8, 10, 12 and 13 on
64-bit Windows, OS X and Linux platforms. 64-bit Windows, OS X and Linux platforms.
Sharp will be built from source at install time when: Sharp will be built from source at install time when:
@@ -74,9 +74,8 @@ libvips is available in the
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev): [community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh ```sh
apk add vips-dev fftw-dev build-base --update-cache \ apk add --upgrade --no-cache vips-dev build-base \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ \ --repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
``` ```
The smaller stack size of musl libc means The smaller stack size of musl libc means
@@ -107,6 +106,8 @@ Only 64-bit (x64) `node.exe` is supported.
### FreeBSD ### FreeBSD
[![FreeBSD Build Status](https://api.cirrus-ci.com/github/lovell/sharp.svg)](https://cirrus-ci.com/github/lovell/sharp)
libvips must be installed before `npm install` is run. libvips must be installed before `npm install` is run.
This can be achieved via package or ports: This can be achieved via package or ports:
@@ -127,6 +128,9 @@ https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior) Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager. to `false` when using the `yarn` package manager.
To reduce the effects of memory fragmentation, add the
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc).
### Docker ### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains an [Marc Bachmann](https://github.com/marcbachmann) maintains an
@@ -247,6 +251,9 @@ SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`. to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
To install the prebuilt sharp binaries from a custom URL, please see
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
### Licences ### Licences
This module is licensed under the terms of the This module is licensed under the terms of the

View File

@@ -4,16 +4,15 @@
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz) * AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab) * Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab)
* Node.js v10.11.0 * Node.js v12.10.0
### The contenders ### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.5.3 - Image processing in pure JavaScript. Provides bicubic interpolation. * [jimp](https://www.npmjs.com/package/jimp) v0.8.4 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.0.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities. * [mapnik](https://www.npmjs.org/package/mapnik) v4.3.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.3 - Wrapper around libmagick++, supports Buffers only.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*". * [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility. * [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.21.0 / libvips v8.7.0 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.23.1 / libvips v8.8.1 - Caching within libvips disabled to ensure a fair comparison.
### The task ### The task
@@ -25,14 +24,14 @@ then compress to JPEG at a "quality" setting of 80.
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.71 | 1.0 | | jimp | buffer | buffer | 0.66 | 1.0 |
| mapnik | buffer | buffer | 3.32 | 4.7 | | mapnik | buffer | buffer | 3.31 | 5.0 |
| gm | buffer | buffer | 3.97 | 5.6 | | gm | buffer | buffer | 3.79 | 5.7 |
| imagemagick-native | buffer | buffer | 4.06 | 5.7 | | gm | file | file | 3.82 | 5.8 |
| imagemagick | file | file | 4.24 | 6.0 | | imagemagick | file | file | 4.17 | 6.3 |
| sharp | stream | stream | 25.30 | 35.6 | | sharp | stream | stream | 25.81 | 39.1 |
| sharp | file | file | 26.17 | 36.9 | | sharp | file | file | 26.76 | 40.5 |
| sharp | buffer | buffer | 26.45 | 37.3 | | sharp | buffer | buffer | 28.06 | 42.5 |
Greater libvips performance can be expected with caching enabled (default) Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches. and using 8+ core machines, especially those with larger L1/L2 CPU caches.

View File

@@ -19,6 +19,9 @@ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SH
const fail = function (err) { const fail = function (err) {
npmLog.error('sharp', err.message); npmLog.error('sharp', err.message);
if (err.code === 'EACCES') {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
}
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error'); npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies'); npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
process.exit(1); process.exit(1);
@@ -79,14 +82,16 @@ try {
npmLog.info('sharp', `Downloading ${url}`); npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) { simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) { if (err) {
throw err; fail(err);
} else if (response.statusCode === 404) {
fail(new Error(`Prebuilt libvips binaries are not yet available for ${platformAndArch}`));
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {
response
.on('error', fail)
.pipe(tmpFile);
} }
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response
.on('error', fail)
.pipe(tmpFile);
}); });
tmpFile tmpFile
.on('error', fail) .on('error', fail)

View File

@@ -20,15 +20,18 @@ function env (key) {
module.exports = function () { module.exports = function () {
try { try {
const proxy = url.parse(proxies.map(env).find(is.string)); const proxy = new url.URL(proxies.map(env).find(is.string));
const tunnel = proxy.protocol === 'https:' const tunnel = proxy.protocol === 'https:'
? tunnelAgent.httpsOverHttps ? tunnelAgent.httpsOverHttps
: tunnelAgent.httpsOverHttp; : tunnelAgent.httpsOverHttp;
const proxyAuth = proxy.username && proxy.password
? `${proxy.username}:${proxy.password}`
: null;
return tunnel({ return tunnel({
proxy: { proxy: {
port: Number(proxy.port), port: Number(proxy.port),
host: proxy.hostname, host: proxy.hostname,
proxyAuth: proxy.auth proxyAuth
} }
}); });
} catch (err) { } catch (err) {

View File

@@ -83,18 +83,22 @@ function toColorspace (colorspace) {
* Update a colour attribute of the this.options Object. * Update a colour attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {String} key
* @param {String|Object} val * @param {String|Object} value
* @throws {Error} Invalid key * @throws {Error} Invalid value
*/ */
function _setColourOption (key, val) { function _setBackgroundColourOption (key, value) {
if (is.object(val) || is.string(val)) { if (is.defined(value)) {
const colour = color(val); if (is.object(value) || is.string(value)) {
this.options[key] = [ const colour = color(value);
colour.red(), this.options[key] = [
colour.green(), colour.red(),
colour.blue(), colour.green(),
Math.round(colour.alpha() * 255) colour.blue(),
]; Math.round(colour.alpha() * 255)
];
} else {
throw is.invalidParameterError('background', 'object or string', value);
}
} }
} }
@@ -111,7 +115,7 @@ module.exports = function (Sharp) {
toColourspace, toColourspace,
toColorspace, toColorspace,
// Private // Private
_setColourOption _setBackgroundColourOption
}); });
// Class attributes // Class attributes
Sharp.colourspace = colourspace; Sharp.colourspace = colourspace;

View File

@@ -81,6 +81,7 @@ const blend = {
* @param {Number} [images[].top] - the pixel offset from the top edge. * @param {Number} [images[].top] - the pixel offset from the top edge.
* @param {Number} [images[].left] - the pixel offset from the left edge. * @param {Number} [images[].left] - the pixel offset from the left edge.
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`. * @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option.
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image. * @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
* @param {Object} [images[].raw] - describes overlay when using raw pixel data. * @param {Object} [images[].raw] - describes overlay when using raw pixel data.
* @param {Number} [images[].raw.width] * @param {Number} [images[].raw.width]
@@ -105,7 +106,8 @@ function composite (images) {
tile: false, tile: false,
left: -1, left: -1,
top: -1, top: -1,
gravity: 0 gravity: 0,
premultiplied: false
}; };
if (is.defined(image.blend)) { if (is.defined(image.blend)) {
if (is.string(blend[image.blend])) { if (is.string(blend[image.blend])) {
@@ -147,6 +149,14 @@ function composite (images) {
throw is.invalidParameterError('gravity', 'valid gravity', image.gravity); throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
} }
} }
if (is.defined(image.premultiplied)) {
if (is.bool(image.premultiplied)) {
composite.premultiplied = image.premultiplied;
} else {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}
return composite; return composite;
}); });
return this; return this;

View File

@@ -78,7 +78,7 @@ const debuglog = util.debuglog('sharp');
* *
* @param {(Buffer|String)} [input] - if present, can be * @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images. * @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
@@ -213,6 +213,7 @@ const Sharp = function (input, options) {
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings

View File

@@ -65,11 +65,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.pages)) { if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages; inputDescriptor.pages = inputOptions.pages;
} else {
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
} }
} }
if (is.defined(inputOptions.page)) { if (is.defined(inputOptions.page)) {
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) { if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
inputDescriptor.page = inputOptions.page; inputDescriptor.page = inputOptions.page;
} else {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
} }
} }
// Create new image // Create new image
@@ -203,6 +207,7 @@ function clone () {
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present * - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present * - `xmp`: Buffer containing raw XMP data, if present
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* *
* @example * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);

View File

@@ -179,7 +179,7 @@ function blur (sigma) {
function flatten (options) { function flatten (options) {
this.options.flatten = is.bool(options) ? options : true; this.options.flatten = is.bool(options) ? options : true;
if (is.object(options)) { if (is.object(options)) {
this._setColourOption('flattenBackground', options.background); this._setBackgroundColourOption('flattenBackground', options.background);
} }
return this; return this;
} }

View File

@@ -10,6 +10,9 @@ const sharp = require('../build/Release/sharp.node');
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported. * with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
* Note that raw pixel data is only supported for buffer output. * Note that raw pixel data is only supported for buffer output.
* *
* By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link withMetadata} for control over this.
*
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
* *
* @example * @example
@@ -57,7 +60,11 @@ function toFile (fileOut, callback) {
/** /**
* Write output to a Buffer. * Write output to a Buffer.
* JPEG, PNG, WebP, TIFF and RAW output are supported. * JPEG, PNG, WebP, TIFF and RAW output are supported.
* By default, the format will match the input image, except GIF and SVG input which become PNG output. *
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link withMetadata} for control over this.
* *
* `callback`, if present, gets three arguments `(err, data, info)` where: * `callback`, if present, gets three arguments `(err, data, info)` where:
* - `err` is an error, if any. * - `err` is an error, if any.
@@ -92,6 +99,8 @@ function toFile (fileOut, callback) {
function toBuffer (options, callback) { function toBuffer (options, callback) {
if (is.object(options)) { if (is.object(options)) {
this._setBooleanOption('resolveWithObject', options.resolveWithObject); this._setBooleanOption('resolveWithObject', options.resolveWithObject);
} else if (this.options.resolveWithObject) {
this.options.resolveWithObject = false;
} }
return this._pipeline(is.fn(options) ? options : callback); return this._pipeline(is.fn(options) ? options : callback);
} }
@@ -487,6 +496,17 @@ function raw () {
return this._updateFormatOut('raw'); return this._updateFormatOut('raw');
} }
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['webp', 'webp']
]);
/** /**
* Force output to a given format. * Force output to a given format.
* *
@@ -502,14 +522,11 @@ function raw () {
* @throws {Error} unsupported format or options * @throws {Error} unsupported format or options
*/ */
function toFormat (format, options) { function toFormat (format, options) {
if (is.object(format) && is.string(format.id)) { const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
format = format.id; if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
} }
if (format === 'jpg') format = 'jpeg'; return this[actualFormat](options);
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
throw is.invalidParameterError('format', 'one of: jpeg, png, webp, tiff, raw', format);
}
return this[format](options);
} }
/** /**
@@ -534,6 +551,7 @@ function toFormat (format, options) {
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192. * @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192. * @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90. * @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. * @param {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).
@@ -557,7 +575,7 @@ function tile (options) {
if (options.overlap > this.options.tileSize) { if (options.overlap > this.options.tileSize) {
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap); throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
} }
this.options.tileOverlap = tile.overlap; this.options.tileOverlap = options.overlap;
} else { } else {
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap); throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
} }
@@ -586,6 +604,8 @@ function tile (options) {
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle); throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
} }
} }
// Background colour
this._setBackgroundColourOption('tileBackground', options.background);
// Depth of tiles // Depth of tiles
if (is.defined(options.depth)) { if (is.defined(options.depth)) {
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) { if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {

View File

@@ -7,6 +7,7 @@ const env = process.env;
module.exports = function () { module.exports = function () {
const arch = env.npm_config_arch || process.arch; const arch = env.npm_config_arch || process.arch;
const platform = env.npm_config_platform || process.platform; const platform = env.npm_config_platform || process.platform;
/* istanbul ignore next */
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : ''; const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
const platformId = [`${platform}${libc}`]; const platformId = [`${platform}${libc}`];

View File

@@ -210,12 +210,20 @@ function resize (width, height, options) {
} }
if (is.object(options)) { if (is.object(options)) {
// Width // Width
if (is.integer(options.width) && options.width > 0) { if (is.defined(options.width)) {
this.options.width = options.width; if (is.integer(options.width) && options.width > 0) {
this.options.width = options.width;
} else {
throw is.invalidParameterError('width', 'positive integer', options.width);
}
} }
// Height // Height
if (is.integer(options.height) && options.height > 0) { if (is.defined(options.height)) {
this.options.height = options.height; if (is.integer(options.height) && options.height > 0) {
this.options.height = options.height;
} else {
throw is.invalidParameterError('height', 'positive integer', options.height);
}
} }
// Fit // Fit
if (is.defined(options.fit)) { if (is.defined(options.fit)) {
@@ -238,9 +246,7 @@ function resize (width, height, options) {
} }
} }
// Background // Background
if (is.defined(options.background)) { this._setBackgroundColourOption('resizeBackground', options.background);
this._setColourOption('resizeBackground', options.background);
}
// Kernel // Kernel
if (is.defined(options.kernel)) { if (is.defined(options.kernel)) {
if (is.string(kernel[options.kernel])) { if (is.string(kernel[options.kernel])) {
@@ -305,7 +311,7 @@ function extend (extend) {
this.options.extendBottom = extend.bottom; this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left; this.options.extendLeft = extend.left;
this.options.extendRight = extend.right; this.options.extendRight = extend.right;
this._setColourOption('extendBackground', extend.background); this._setBackgroundColourOption('extendBackground', extend.background);
} else { } else {
throw is.invalidParameterError('extend', 'integer or object', extend); throw is.invalidParameterError('extend', 'integer or object', extend);
} }
@@ -361,7 +367,10 @@ function extract (options) {
/** /**
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel. * Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
*
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
*
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero. * @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters

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.23.0", "version": "0.23.4",
"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": [
@@ -61,12 +61,15 @@
"Keith Belovay <keith@picthrive.com>", "Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>", "Michael B. Klein <mbklein@gmail.com>",
"Jordan Prudhomme <jordan@raboland.fr>", "Jordan Prudhomme <jordan@raboland.fr>",
"Ilya Ovdin <iovdin@gmail.com>" "Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@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) || (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 && cc && 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",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"", "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
@@ -74,6 +77,14 @@
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done" "docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"files": [
"binding.gyp",
"docs/**",
"!docs/css/**",
"install/**",
"lib/**",
"src/**"
],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/lovell/sharp" "url": "git://github.com/lovell/sharp"
@@ -99,27 +110,27 @@
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "nan": "^2.14.0",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.0", "prebuild-install": "^5.3.3",
"semver": "^6.3.0", "semver": "^6.3.0",
"simple-get": "^3.0.3", "simple-get": "^3.1.0",
"tar": "^4.4.10", "tar": "^5.0.5",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.0", "async": "^3.1.0",
"cc": "^1.0.2", "cc": "^2.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.2",
"documentation": "^12.0.3", "documentation": "^12.1.4",
"exif-reader": "^1.0.2", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^1.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^6.2.0", "mocha": "^6.2.2",
"mock-fs": "^4.10.1", "mock-fs": "^4.10.4",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"prebuild": "^9.0.1", "prebuild": "^9.1.1",
"prebuild-ci": "^3.1.0", "prebuild-ci": "^3.1.0",
"rimraf": "^2.6.3", "rimraf": "^3.0.0",
"semistandard": "^13.0.1" "semistandard": "^14.2.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
@@ -128,6 +139,9 @@
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=8.5.0"
}, },
"funding": {
"url": "https://opencollective.com/libvips"
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"
@@ -136,10 +150,7 @@
"cc": { "cc": {
"linelength": "120", "linelength": "120",
"filter": [ "filter": [
"build/c++11", "build/include"
"build/include",
"runtime/indentation_namespace",
"runtime/references"
] ]
} }
} }

View File

@@ -17,7 +17,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <mutex> #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <node.h>
#include <node_buffer.h> #include <node_buffer.h>
@@ -58,6 +58,7 @@ namespace sharp {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = node::Buffer::Data(buffer);
descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer); buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrTo<bool>(input, "failOnError");
@@ -147,7 +148,7 @@ namespace sharp {
case ImageType::OPENSLIDE: id = "openslide"; break; case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break; case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break; case ImageType::FITS: id = "fits"; break;
case ImageType::VIPS: id = "v"; break; case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
case ImageType::MISSING: id = "missing"; break; case ImageType::MISSING: id = "missing"; break;
@@ -246,7 +247,7 @@ namespace sharp {
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) { std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
VImage image; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->buffer != nullptr) { if (descriptor->isBuffer) {
if (descriptor->rawChannels > 0) { if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength, image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@@ -277,7 +278,7 @@ namespace sharp {
} }
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} }
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what()); throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
@@ -323,7 +324,7 @@ namespace sharp {
} }
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} }
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what()); throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
@@ -370,15 +371,19 @@ namespace sharp {
/* /*
Set EXIF Orientation of image. Set EXIF Orientation of image.
*/ */
void SetExifOrientation(VImage image, int const orientation) { VImage SetExifOrientation(VImage image, int const orientation) {
image.set(VIPS_META_ORIENTATION, orientation); VImage copy = image.copy();
copy.set(VIPS_META_ORIENTATION, orientation);
return copy;
} }
/* /*
Remove EXIF Orientation from image. Remove EXIF Orientation from image.
*/ */
void RemoveExifOrientation(VImage image) { VImage RemoveExifOrientation(VImage image) {
vips_image_remove(image.get_image(), VIPS_META_ORIENTATION); VImage copy = image.copy();
copy.remove(VIPS_META_ORIENTATION);
return copy;
} }
/* /*
@@ -398,11 +403,13 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const double density) { VImage SetDensity(VImage image, const double density) {
const double pixelsPerMm = density / 25.4; const double pixelsPerMm = density / 25.4;
image.set("Xres", pixelsPerMm); VImage copy = image.copy();
image.set("Yres", pixelsPerMm); copy.set("Xres", pixelsPerMm);
image.set(VIPS_META_RESOLUTION_UNIT, "in"); copy.set("Yres", pixelsPerMm);
copy.set(VIPS_META_RESOLUTION_UNIT, "in");
return copy;
} }
/* /*

View File

@@ -43,12 +43,13 @@ using vips::VImage;
namespace sharp { namespace sharp {
struct InputDescriptor { struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
std::string name; std::string name;
std::string file; std::string file;
char *buffer; char *buffer;
bool failOnError; bool failOnError;
size_t bufferLength; size_t bufferLength;
bool isBuffer;
double density; double density;
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
@@ -64,6 +65,7 @@ namespace sharp {
buffer(nullptr), buffer(nullptr),
failOnError(TRUE), failOnError(TRUE),
bufferLength(0), bufferLength(0),
isBuffer(FALSE),
density(72.0), density(72.0),
rawChannels(0), rawChannels(0),
rawWidth(0), rawWidth(0),
@@ -92,7 +94,7 @@ namespace sharp {
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -175,12 +177,12 @@ namespace sharp {
/* /*
Set EXIF Orientation of image. Set EXIF Orientation of image.
*/ */
void SetExifOrientation(VImage image, int const orientation); VImage SetExifOrientation(VImage image, int const orientation);
/* /*
Remove EXIF Orientation from image. Remove EXIF Orientation from image.
*/ */
void RemoveExifOrientation(VImage image); VImage RemoveExifOrientation(VImage image);
/* /*
Does this image have a non-default density? Does this image have a non-default density?
@@ -195,7 +197,7 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const double density); VImage SetDensity(VImage image, const double density);
/* /*
Check the proposed format supports the current dimensions. Check the proposed format supports the current dimensions.

View File

@@ -116,6 +116,14 @@ class MetadataWorker : public Nan::AsyncWorker {
memcpy(baton->xmp, xmp, xmpLength); memcpy(baton->xmp, xmp, xmpLength);
baton->xmpLength = xmpLength; baton->xmpLength = xmpLength;
} }
// TIFFTAG_PHOTOSHOP
if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) {
size_t tifftagPhotoshopLength;
void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength);
baton->tifftagPhotoshop = static_cast<char *>(g_malloc(tifftagPhotoshopLength));
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
}
} }
// Clean up // Clean up
@@ -189,6 +197,12 @@ class MetadataWorker : public Nan::AsyncWorker {
New("xmp").ToLocalChecked(), New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).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; argv[1] = info;
} }

View File

@@ -48,6 +48,8 @@ struct MetadataBaton {
size_t iptcLength; size_t iptcLength;
char *xmp; char *xmp;
size_t xmpLength; size_t xmpLength;
char *tifftagPhotoshop;
size_t tifftagPhotoshopLength;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():
@@ -71,7 +73,9 @@ struct MetadataBaton {
iptc(nullptr), iptc(nullptr),
iptcLength(0), iptcLength(0),
xmp(nullptr), xmp(nullptr),
xmpLength(0) {} xmpLength(0),
tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); NAN_METHOD(metadata);

View File

@@ -191,12 +191,20 @@ namespace sharp {
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image) return RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_LCH) .colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)}) .linear(
{ brightness, saturation, 1},
{ 0.0, 0.0, static_cast<double>(hue) }
)
.colourspace(VIPS_INTERPRETATION_sRGB)
.bandjoin(alpha); .bandjoin(alpha);
} else { } else {
return image return image
.colourspace(VIPS_INTERPRETATION_LCH) .colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)}); .linear(
{ brightness, saturation, 1 },
{ 0.0, 0.0, static_cast<double>(hue) }
)
.colourspace(VIPS_INTERPRETATION_sRGB);
} }
} }
@@ -250,17 +258,30 @@ namespace sharp {
Trim an image Trim an image
*/ */
VImage Trim(VImage image, double const threshold) { VImage Trim(VImage image, double const threshold) {
if (image.width() < 3 && image.height() < 3) {
throw VError("Image to trim must be at least 3x3 pixels");
}
// Top-left pixel provides the background colour // Top-left pixel provides the background colour
VImage background = image.extract_area(0, 0, 1, 1); VImage background = image.extract_area(0, 0, 1, 1);
if (HasAlpha(background)) { if (HasAlpha(background)) {
background = background.flatten(); background = background.flatten();
} }
int top, width, height; int left, top, width, height;
int const left = image.find_trim(&top, &width, &height, VImage::option() left = image.find_trim(&top, &width, &height, VImage::option()
->set("background", background(0, 0)) ->set("background", background(0, 0))
->set("threshold", threshold)); ->set("threshold", threshold));
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance"); if (HasAlpha(image)) {
// Search alpha channel
VImage alpha = image[image.bands() - 1];
VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
left = alpha.find_trim(&top, &width, &height, VImage::option()
->set("background", backgroundAlpha(0, 0))
->set("threshold", threshold));
}
if (width == 0 || height == 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance");
}
} }
return image.extract_area(left, top, width, height); return image.extract_area(left, top, width, height);
} }

View File

@@ -38,6 +38,9 @@
#elif defined(__APPLE__) #elif defined(__APPLE__)
#define STAT64_STRUCT stat #define STAT64_STRUCT stat
#define STAT64_FUNCTION stat #define STAT64_FUNCTION stat
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#else #else
#define STAT64_STRUCT stat64 #define STAT64_STRUCT stat64
#define STAT64_FUNCTION stat64 #define STAT64_FUNCTION stat64
@@ -101,7 +104,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (baton->rotateBeforePreExtract) { if (baton->rotateBeforePreExtract) {
if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation); image = image.rot(rotation);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
if (baton->rotationAngle != 0.0) { if (baton->rotationAngle != 0.0) {
std::vector<double> background; std::vector<double> background;
@@ -401,20 +404,20 @@ class PipelineWorker : public Nan::AsyncWorker {
// Rotate post-extract 90-angle // Rotate post-extract 90-angle
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) { if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation); image = image.rot(rotation);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Flip (mirror about Y axis) // Flip (mirror about Y axis)
if (baton->flip) { if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL); image = image.flip(VIPS_DIRECTION_VERTICAL);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Flop (mirror about X axis) // Flop (mirror about X axis)
if (baton->flop) { if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL); image = image.flip(VIPS_DIRECTION_HORIZONTAL);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Join additional color channels to the image // Join additional color channels to the image
@@ -591,7 +594,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (!HasAlpha(compositeImage)) { if (!HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage); compositeImage = sharp::EnsureAlpha(compositeImage);
} }
compositeImage = compositeImage.premultiply(); if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
// Calculate position // Calculate position
int left; int left;
int top; int top;
@@ -697,7 +700,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Override EXIF Orientation tag // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
} }
// Number of channels used in output image // Number of channels used in output image
@@ -959,6 +962,11 @@ class PipelineWorker : public Nan::AsyncWorker {
}; };
suffix = AssembleSuffixString(extname, options); suffix = AssembleSuffixString(extname, options);
} }
// Remove alpha channel from tile background if image does not contain an alpha channel
if (!HasAlpha(image)) {
baton->tileBackground.pop_back();
}
// Write DZ to file // Write DZ to file
vips::VOption *options = VImage::option() vips::VOption *options = VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@@ -968,6 +976,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("layout", baton->tileLayout) ->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data())) ->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle)) ->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("skip_blanks", baton->tileSkipBlanks); ->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by // libvips chooses a default depth based on layout. Instead of replicating that logic here by
@@ -1230,6 +1239,7 @@ NAN_METHOD(pipeline) {
composite->left = AttrTo<int32_t>(compositeObject, "left"); composite->left = AttrTo<int32_t>(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top"); composite->top = AttrTo<int32_t>(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile"); composite->tile = AttrTo<bool>(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied");
baton->composite.push_back(composite); baton->composite.push_back(composite);
} }
// Resize options // Resize options
@@ -1373,6 +1383,7 @@ NAN_METHOD(pipeline) {
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer"); std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle"); baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->tileBackground = AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks"); baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
if (tileContainer == "zip") { if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;

View File

@@ -41,6 +41,7 @@ struct Composite {
int left; int left;
int top; int top;
bool tile; bool tile;
bool premultiplied;
Composite(): Composite():
input(nullptr), input(nullptr),
@@ -48,7 +49,8 @@ struct Composite {
gravity(0), gravity(0),
left(-1), left(-1),
top(-1), top(-1),
tile(false) {} tile(false),
premultiplied(false) {}
}; };
struct PipelineBaton { struct PipelineBaton {
@@ -173,6 +175,7 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
std::string tileFormat; std::string tileFormat;
int tileAngle; int tileAngle;
std::vector<double> tileBackground;
int tileSkipBlanks; int tileSkipBlanks;
VipsForeignDzDepth tileDepth; VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix; std::unique_ptr<double[]> recombMatrix;
@@ -278,6 +281,7 @@ struct PipelineBaton {
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ), tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
tileAngle(0), tileAngle(0),
tileBackground{ 255.0, 255.0, 255.0, 255.0 },
tileSkipBlanks(-1), tileSkipBlanks(-1),
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {} tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
}; };

View File

@@ -150,8 +150,9 @@ NAN_METHOD(format) {
// Which load/save operations are available for each compressed format? // Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>(); Local<Object> format = New<Object>();
for (std::string f : { for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "heif", "pdf", "v" "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
}) { }) {
// Input // Input
Local<Boolean> hasInputFile = Local<Boolean> hasInputFile =

View File

@@ -12,10 +12,9 @@
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"gm": "^1.23.1", "gm": "^1.23.1",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.3", "jimp": "^0.8.4",
"jimp": "^0.6.4", "mapnik": "^4.3.1",
"mapnik": "^4.2.1", "semver": "^6.3.0"
"semver": "^6.1.2"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {

View File

@@ -12,12 +12,6 @@ const gm = require('gm');
const imagemagick = require('imagemagick'); const imagemagick = require('imagemagick');
const mapnik = require('mapnik'); const mapnik = require('mapnik');
const jimp = require('jimp'); const jimp = require('jimp');
let imagemagickNative;
try {
imagemagickNative = require('imagemagick-native');
} catch (err) {
console.log('Excluding imagemagick-native');
}
const fixtures = require('../fixtures'); const fixtures = require('../fixtures');
@@ -28,7 +22,7 @@ const height = 588;
sharp.cache(false); sharp.cache(false);
async.series({ async.series({
'jpeg': function (callback) { jpeg: function (callback) {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg); const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
const jpegSuite = new Benchmark.Suite('jpeg'); const jpegSuite = new Benchmark.Suite('jpeg');
// jimp // jimp
@@ -126,29 +120,6 @@ async.series({
}); });
} }
}); });
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
jpegSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function (deferred) {
imagemagickNative.convert({
srcData: inputJpgBuffer,
quality: 80,
width: width,
height: height,
format: 'JPEG',
filter: 'Lanczos'
}, function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
}
// gm // gm
jpegSuite.add('gm-buffer-file', { jpegSuite.add('gm-buffer-file', {
defer: true, defer: true,
@@ -289,6 +260,9 @@ async.series({
.then(function (buffer) { .then(function (buffer) {
assert.notStrictEqual(null, buffer); assert.notStrictEqual(null, buffer);
deferred.resolve(); deferred.resolve();
})
.catch(function (err) {
throw err;
}); });
} }
}).on('cycle', function (event) { }).on('cycle', function (event) {
@@ -698,22 +672,6 @@ async.series({
}); });
} }
}); });
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
pngSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function (deferred) {
imagemagickNative.convert({
srcData: inputPngBuffer,
width: width,
height: height,
format: 'PNG',
filter: 'Lanczos'
});
deferred.resolve();
}
});
}
// gm // gm
pngSuite.add('gm-file-file', { pngSuite.add('gm-file-file', {
defer: true, defer: true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
test/fixtures/image-in-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -13,6 +13,7 @@ const getPath = function (filename) {
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html // Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
const fingerprint = function (image, callback) { const fingerprint = function (image, callback) {
sharp(image) sharp(image)
.flatten('gray')
.greyscale() .greyscale()
.normalise() .normalise()
.resize(9, 8, { fit: sharp.fit.fill }) .resize(9, 8, { fit: sharp.fit.fill })
@@ -87,6 +88,7 @@ module.exports = {
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0 inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg) inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
@@ -95,6 +97,7 @@ module.exports = {
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
inputTiff8BitDepth: getPath('8bit_depth.tiff'), inputTiff8BitDepth: getPath('8bit_depth.tiff'),
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
test/fixtures/tifftag-photoshop.tiff vendored Normal file

Binary file not shown.

View File

@@ -54,7 +54,7 @@ describe('Alpha transparency', function () {
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width); assert.strictEqual(32, info.width);
assert.strictEqual(32, info.height); assert.strictEqual(32, info.height);
fixtures.assertMaxColourDistance(output, fixtures.expected('flatten-rgb16-orange.jpg'), 25); fixtures.assertMaxColourDistance(output, fixtures.expected('flatten-rgb16-orange.jpg'), 10);
done(); done();
}); });
}); });
@@ -81,6 +81,18 @@ describe('Alpha transparency', function () {
}); });
}); });
it('Flatten with options but without colour does not throw', () => {
assert.doesNotThrow(() => {
sharp().flatten({});
});
});
it('Flatten to invalid colour throws', () => {
assert.throws(() => {
sharp().flatten({ background: 1 });
});
});
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function () { it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function () {
const base = 'alpha-premultiply-enlargement-2048x1536-paper.png'; const base = 'alpha-premultiply-enlargement-2048x1536-paper.png';
const actual = fixtures.path('output.' + base); const actual = fixtures.path('output.' + base);

View File

@@ -62,6 +62,65 @@ describe('composite', () => {
}) })
)); ));
it('premultiplied true', () => {
const filename = 'composite.premultiplied.png';
const below = fixtures.path(`input.below.${filename}`);
const above = fixtures.path(`input.above.${filename}`);
const actual = fixtures.path(`output.true.${filename}`);
const expected = fixtures.expected(`expected.true.${filename}`);
return sharp(below)
.composite([{
input: above,
blend: 'color-burn',
top: 0,
left: 0,
premultiplied: true
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('premultiplied false', () => {
const filename = 'composite.premultiplied.png';
const below = fixtures.path(`input.below.${filename}`);
const above = fixtures.path(`input.above.${filename}`);
const actual = fixtures.path(`output.false.${filename}`);
const expected = fixtures.expected(`expected.false.${filename}`);
return sharp(below)
.composite([{
input: above,
blend: 'color-burn',
top: 0,
left: 0,
premultiplied: false
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('premultiplied absent', () => {
const filename = 'composite.premultiplied.png';
const below = fixtures.path(`input.below.${filename}`);
const above = fixtures.path(`input.above.${filename}`);
const actual = fixtures.path(`output.absent.${filename}`);
const expected = fixtures.expected(`expected.absent.${filename}`);
return sharp(below)
.composite([{
input: above,
blend: 'color-burn',
top: 0,
left: 0
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('multiple', () => { it('multiple', () => {
const filename = 'composite-multiple.png'; const filename = 'composite-multiple.png';
const actual = fixtures.path(`output.${filename}`); const actual = fixtures.path(`output.${filename}`);
@@ -265,6 +324,12 @@ describe('composite', () => {
}, /Expected boolean for tile but received invalid of type string/); }, /Expected boolean for tile but received invalid of type string/);
}); });
it('invalid premultiplied', () => {
assert.throws(() => {
sharp().composite([{ input: 'test', premultiplied: 'invalid' }]);
}, /Expected boolean for premultiplied but received invalid of type string/);
});
it('invalid left', () => { it('invalid left', () => {
assert.throws(() => { assert.throws(() => {
sharp().composite([{ input: 'test', left: 0.5 }]); sharp().composite([{ input: 'test', left: 0.5 }]);

View File

@@ -648,6 +648,16 @@ describe('Input/output', function () {
it('Ignore unknown attribute', function () { it('Ignore unknown attribute', function () {
sharp(null, { unknown: true }); sharp(null, { unknown: true });
}); });
it('Invalid page property throws', function () {
assert.throws(function () {
sharp(null, { page: -1 });
}, /Expected integer between 0 and 100000 for page but received -1 of type number/);
});
it('Invalid pages property throws', function () {
assert.throws(function () {
sharp(null, { pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
});
}); });
describe('create new image', function () { describe('create new image', function () {

View File

@@ -259,4 +259,35 @@ describe('JPEG', function () {
}); });
}); });
}); });
it('Specifying quantization table provides different JPEG', function (done) {
// First generate with default quantization table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false })
.toBuffer(function (err, withDefaultQuantizationTable, withInfo) {
if (err) throw err;
assert.strictEqual(true, withDefaultQuantizationTable.length > 0);
assert.strictEqual(withDefaultQuantizationTable.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Then generate with different quantization table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false, quantizationTable: 3 })
.toBuffer(function (err, withQuantTable3, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withQuantTable3.length > 0);
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantizationTable.length);
done();
});
});
});
}); });

View File

@@ -73,7 +73,7 @@ describe('Linear adjustment', function () {
assert.throws(function () { assert.throws(function () {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputPngOverlayLayer1)
.linear(undefined, { 'bar': 'baz' }); .linear(undefined, { bar: 'baz' });
}); });
}); });
}); });

View File

@@ -232,6 +232,26 @@ describe('Image metadata', function () {
done(); done();
}); });
}); });
it('vips', () =>
sharp(fixtures.inputV)
.metadata()
.then(metadata => {
assert.strictEqual('vips', metadata.format);
assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(70, metadata.width);
assert.strictEqual(60, metadata.height);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density);
assert.strictEqual('undefined', typeof metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
})
);
it('File in, Promise out', function (done) { it('File in, Promise out', function (done) {
sharp(fixtures.inputJpg).metadata().then(function (metadata) { sharp(fixtures.inputJpg).metadata().then(function (metadata) {
@@ -287,6 +307,20 @@ describe('Image metadata', function () {
readable.pipe(pipeline); readable.pipe(pipeline);
}); });
it('Stream in, rejected Promise out', () => {
const pipeline = sharp();
fs
.createReadStream(__filename)
.pipe(pipeline);
return pipeline
.metadata()
.then(
() => Promise.reject(new Error('Expected metadata to reject')),
err => assert.strictEqual(err.message, 'Input buffer contains unsupported image format')
);
});
it('Stream', function (done) { it('Stream', function (done) {
const readable = fs.createReadStream(fixtures.inputJpg); const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().metadata(function (err, metadata) { const pipeline = sharp().metadata(function (err, metadata) {
@@ -370,6 +404,34 @@ describe('Image metadata', function () {
}); });
}); });
it('Include metadata in output, enabled via empty object', () =>
sharp(fixtures.inputJpgWithExif)
.withMetadata({})
.toBuffer()
.then((buffer) => sharp(buffer)
.metadata()
.then(metadata => {
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(8, metadata.orientation);
assert.strictEqual('object', typeof metadata.exif);
assert.strictEqual(true, metadata.exif instanceof Buffer);
// EXIF
const exif = exifReader(metadata.exif);
assert.strictEqual('object', typeof exif);
assert.strictEqual('object', typeof exif.image);
assert.strictEqual('number', typeof exif.image.XResolution);
// ICC
assert.strictEqual('object', typeof metadata.icc);
assert.strictEqual(true, metadata.icc instanceof Buffer);
const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile);
assert.strictEqual('RGB', profile.colorSpace);
assert.strictEqual('Perceptual', profile.intent);
assert.strictEqual('Monitor', profile.deviceClass);
})
)
);
it('Remove EXIF metadata after a resize', function (done) { it('Remove EXIF metadata after a resize', function (done) {
sharp(fixtures.inputJpgWithExif) sharp(fixtures.inputJpgWithExif)
.resize(320, 240) .resize(320, 240)
@@ -453,6 +515,21 @@ describe('Image metadata', function () {
}); });
}); });
it('16-bit TIFF with TIFFTAG_PHOTOSHOP metadata', () =>
sharp(fixtures.inputTifftagPhotoshop)
.metadata()
.then(metadata => {
assert.strictEqual(metadata.format, 'tiff');
assert.strictEqual(metadata.width, 317);
assert.strictEqual(metadata.height, 211);
assert.strictEqual(metadata.space, 'rgb16');
assert.strictEqual(metadata.channels, 3);
assert.strictEqual(typeof metadata.tifftagPhotoshop, 'object');
assert.strictEqual(metadata.tifftagPhotoshop instanceof Buffer, true);
assert.strictEqual(metadata.tifftagPhotoshop.length, 6634);
})
);
it('File input with corrupt header fails gracefully', function (done) { it('File input with corrupt header fails gracefully', function (done) {
sharp(fixtures.inputJpgWithCorruptHeader) sharp(fixtures.inputJpgWithCorruptHeader)
.metadata(function (err) { .metadata(function (err) {

View File

@@ -122,4 +122,21 @@ describe('Modulate', function () {
}); });
}); });
}); });
it('should be able to use linear and modulate together', function () {
const base = 'modulate-linear.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
const contrast = 1.5;
const brightness = 0.5;
return sharp(fixtures.testPattern)
.linear(contrast, -(128 * contrast) + 128)
.modulate({ brightness })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected);
});
});
}); });

View File

@@ -53,7 +53,8 @@ describe('Raw pixel data', function () {
width: info.width, width: info.width,
height: info.height, height: info.height,
channels: info.channels channels: info.channels
} }) }
})
.jpeg() .jpeg()
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;
@@ -81,7 +82,8 @@ describe('Raw pixel data', function () {
width: info.width, width: info.width,
height: info.height, height: info.height,
channels: info.channels channels: info.channels
} }) }
})
.png() .png()
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;

View File

@@ -87,6 +87,18 @@ describe('Resize dimensions', function () {
}, /Expected positive integer for height but received 1.5 of type number/); }, /Expected positive integer for height but received 1.5 of type number/);
}); });
it('Invalid width - via options', () => {
assert.throws(() => {
sharp().resize({ width: 1.5, height: 240 });
}, /Expected positive integer for width but received 1.5 of type number/);
});
it('Invalid height - via options', () => {
assert.throws(() => {
sharp().resize({ width: 320, height: 1.5 });
}, /Expected positive integer for height but received 1.5 of type number/);
});
it('Invalid width - too large', function (done) { it('Invalid width - too large', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(0x4000, 1) .resize(0x4000, 1)

View File

@@ -27,52 +27,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 615101275)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 615101275));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 83061892917)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 83061892917));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 101.44954540768993)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 101.44954540768993));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 58.373870588815414)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 58.373870588815414));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 462824115)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 462824115));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 47083677255)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 47083677255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 76.33425255128337)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 76.33425255128337));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 44.03023262954866)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 44.03023262954866));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 2725));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 372986756)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 372986756));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 32151543524)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 32151543524));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 61.51724663436759)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 61.51724663436759));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 38.96702865090125)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 38.96702865090125));
assert.strictEqual(true, isInteger(stats.channels[2]['minX'])); assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['minY'])); assert.strictEqual(true, isInteger(stats.channels[2].minY));
assert.strictEqual(true, isInRange(stats.channels[2]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[2].maxX));
assert.strictEqual(true, isInRange(stats.channels[2]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[2].maxY));
assert.strictEqual(true, isInRange(stats.channels[2]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].maxY, 0, 2725));
done(); done();
}); });
@@ -86,20 +86,20 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 1391368230)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 1391368230));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 354798898650)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 354798898650));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 238.8259925648822)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 238.8259925648822));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 62.15121915523771)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 62.15121915523771));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2809)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2809));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2074)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2074));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2809)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2809));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2074)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2074));
done(); done();
}); });
}); });
@@ -112,68 +112,68 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 795678795)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 795678795));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 202898092725)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 202898092725));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 252.9394769668579)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 252.9394769668579));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 22.829537532816)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 22.829537532816));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 1536));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 1536));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 795678795)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 795678795));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 202898092725)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 202898092725));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 252.9394769668579)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 252.9394769668579));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 22.829537532816)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 22.829537532816));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 1536));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 1536));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 795678795)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 795678795));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 202898092725)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 202898092725));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 252.9394769668579)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 252.9394769668579));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 22.829537532816)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 22.829537532816));
assert.strictEqual(true, isInteger(stats.channels[2]['minX'])); assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2]['minX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[2]['minY'])); assert.strictEqual(true, isInteger(stats.channels[2].minY));
assert.strictEqual(true, isInRange(stats.channels[2]['minY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[2].minY, 0, 1536));
assert.strictEqual(true, isInteger(stats.channels[2]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[2].maxX));
assert.strictEqual(true, isInRange(stats.channels[2]['maxX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[2].maxX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[2]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[2].maxY));
assert.strictEqual(true, isInRange(stats.channels[2]['maxY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[2].maxY, 0, 1536));
// alpha channel // alpha channel
assert.strictEqual(0, stats.channels[3]['min']); assert.strictEqual(0, stats.channels[3].min);
assert.strictEqual(255, stats.channels[3]['max']); assert.strictEqual(255, stats.channels[3].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['sum'], 5549142)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].sum, 5549142));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['squaresSum'], 1333571132)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].squaresSum, 1333571132));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['mean'], 1.7640247344970703)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].mean, 1.7640247344970703));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['stdev'], 20.51387814157297)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].stdev, 20.51387814157297));
assert.strictEqual(true, isInteger(stats.channels[3]['minX'])); assert.strictEqual(true, isInteger(stats.channels[3].minX));
assert.strictEqual(true, isInRange(stats.channels[3]['minX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[3].minX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[3]['minY'])); assert.strictEqual(true, isInteger(stats.channels[3].minY));
assert.strictEqual(true, isInRange(stats.channels[3]['minY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[3].minY, 0, 1536));
assert.strictEqual(true, isInteger(stats.channels[3]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[3].maxX));
assert.strictEqual(true, isInRange(stats.channels[3]['maxX'], 0, 2048)); assert.strictEqual(true, isInRange(stats.channels[3].maxX, 0, 2048));
assert.strictEqual(true, isInteger(stats.channels[3]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[3].maxY));
assert.strictEqual(true, isInRange(stats.channels[3]['maxY'], 0, 1536)); assert.strictEqual(true, isInRange(stats.channels[3].maxY, 0, 1536));
done(); done();
}); });
@@ -187,20 +187,20 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
// alpha channel // alpha channel
assert.strictEqual(0, stats.channels[3]['min']); assert.strictEqual(0, stats.channels[3].min);
assert.strictEqual(0, stats.channels[3]['max']); assert.strictEqual(0, stats.channels[3].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['sum'], 0)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].sum, 0));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['squaresSum'], 0)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].squaresSum, 0));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['mean'], 0)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].mean, 0));
assert.strictEqual(true, isInAcceptableRange(stats.channels[3]['stdev'], 0)); assert.strictEqual(true, isInAcceptableRange(stats.channels[3].stdev, 0));
assert.strictEqual(true, isInteger(stats.channels[3]['minX'])); assert.strictEqual(true, isInteger(stats.channels[3].minX));
assert.strictEqual(true, isInRange(stats.channels[3]['minX'], 0, 300)); assert.strictEqual(true, isInRange(stats.channels[3].minX, 0, 300));
assert.strictEqual(true, isInteger(stats.channels[3]['minY'])); assert.strictEqual(true, isInteger(stats.channels[3].minY));
assert.strictEqual(true, isInRange(stats.channels[3]['minY'], 0, 300)); assert.strictEqual(true, isInRange(stats.channels[3].minY, 0, 300));
assert.strictEqual(true, isInteger(stats.channels[3]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[3].maxX));
assert.strictEqual(true, isInRange(stats.channels[3]['maxX'], 0, 300)); assert.strictEqual(true, isInRange(stats.channels[3].maxX, 0, 300));
assert.strictEqual(true, isInteger(stats.channels[3]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[3].maxY));
assert.strictEqual(true, isInRange(stats.channels[3]['maxY'], 0, 300)); assert.strictEqual(true, isInRange(stats.channels[3].maxY, 0, 300));
done(); done();
}); });
@@ -214,20 +214,20 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 1887266220)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 1887266220));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 481252886100)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 481252886100));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 235.81772349417824)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 235.81772349417824));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 67.25712856093298)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 67.25712856093298));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2464)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2464));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 3248)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 3248));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2464)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2464));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 3248)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 3248));
done(); done();
}); });
@@ -241,52 +241,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 83291370)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 83291370));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 11379783198)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11379783198));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 105.36169496842616)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 105.36169496842616));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 57.39412151419967)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.39412151419967));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 772));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 772));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 120877425)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120877425));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 20774687595)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20774687595));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 152.9072025279307)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 152.9072025279307));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 53.84143349689916)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.84143349689916));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 772));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 772));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 138938859)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138938859));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 28449125593)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28449125593));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 175.75450711423252)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 175.75450711423252));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 71.39929031070358)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.39929031070358));
assert.strictEqual(true, isInteger(stats.channels[2]['minX'])); assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2]['minX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[2]['minY'])); assert.strictEqual(true, isInteger(stats.channels[2].minY));
assert.strictEqual(true, isInRange(stats.channels[2]['minY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[2].minY, 0, 772));
assert.strictEqual(true, isInteger(stats.channels[2]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[2].maxX));
assert.strictEqual(true, isInRange(stats.channels[2]['maxX'], 0, 1024)); assert.strictEqual(true, isInRange(stats.channels[2].maxX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[2]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[2].maxY));
assert.strictEqual(true, isInRange(stats.channels[2]['maxY'], 0, 772)); assert.strictEqual(true, isInRange(stats.channels[2].maxY, 0, 772));
done(); done();
}); });
@@ -300,52 +300,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
// red channel // red channel
assert.strictEqual(35, stats.channels[0]['min']); assert.strictEqual(35, stats.channels[0].min);
assert.strictEqual(254, stats.channels[0]['max']); assert.strictEqual(254, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 56088385)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 56088385));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 8002132113)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 8002132113));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 131.53936444652908)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 131.53936444652908));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 38.26389131415863)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 38.26389131415863));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 533));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 533));
// green channel // green channel
assert.strictEqual(43, stats.channels[1]['min']); assert.strictEqual(43, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 58612156)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 58612156));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 8548344254)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 8548344254));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 137.45815196998123)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 137.45815196998123));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 33.955424103758205)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 33.955424103758205));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 533));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 533));
// blue channel // blue channel
assert.strictEqual(51, stats.channels[2]['min']); assert.strictEqual(51, stats.channels[2].min);
assert.strictEqual(254, stats.channels[2]['max']); assert.strictEqual(254, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 49628525)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 49628525));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 6450556071)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 6450556071));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 116.38959896810506)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 116.38959896810506));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 39.7669551046809)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 39.7669551046809));
assert.strictEqual(true, isInteger(stats.channels[2]['minX'])); assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2]['minX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[2]['minY'])); assert.strictEqual(true, isInteger(stats.channels[2].minY));
assert.strictEqual(true, isInRange(stats.channels[2]['minY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[2].minY, 0, 533));
assert.strictEqual(true, isInteger(stats.channels[2]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[2].maxX));
assert.strictEqual(true, isInRange(stats.channels[2]['maxX'], 0, 800)); assert.strictEqual(true, isInRange(stats.channels[2].maxX, 0, 800));
assert.strictEqual(true, isInteger(stats.channels[2]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[2].maxY));
assert.strictEqual(true, isInRange(stats.channels[2]['maxY'], 0, 533)); assert.strictEqual(true, isInRange(stats.channels[2].maxY, 0, 533));
done(); done();
}); });
@@ -359,36 +359,36 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
// gray channel // gray channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(101, stats.channels[0]['max']); assert.strictEqual(101, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 101)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 101));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 10201)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 10201));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 50.5)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 50.5));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 71.4177848998413)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 71.4177848998413));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 1)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 1));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 1)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 1));
// alpha channel // alpha channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 255)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 65025)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 65025));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 127.5)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 127.5));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 180.31222920256963)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 180.31222920256963));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 2)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 2));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 1)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 1));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 2)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 2));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 1)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 1));
done(); done();
}); });
@@ -412,52 +412,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 615101275)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 615101275));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 83061892917)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 83061892917));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 101.44954540768993)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 101.44954540768993));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 58.373870588815414)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 58.373870588815414));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 462824115)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 462824115));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 47083677255)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 47083677255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 76.33425255128337)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 76.33425255128337));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 44.03023262954866)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 44.03023262954866));
assert.strictEqual(true, isInteger(stats.channels[1]['minX'])); assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['minY'])); assert.strictEqual(true, isInteger(stats.channels[1].minY));
assert.strictEqual(true, isInRange(stats.channels[1]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[1].maxX));
assert.strictEqual(true, isInRange(stats.channels[1]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[1]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[1].maxY));
assert.strictEqual(true, isInRange(stats.channels[1]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[1].maxY, 0, 2725));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 372986756)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 372986756));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 32151543524)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 32151543524));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 61.51724663436759)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 61.51724663436759));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 38.96702865090125)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 38.96702865090125));
assert.strictEqual(true, isInteger(stats.channels[2]['minX'])); assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['minY'])); assert.strictEqual(true, isInteger(stats.channels[2].minY));
assert.strictEqual(true, isInRange(stats.channels[2]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[2].maxX));
assert.strictEqual(true, isInRange(stats.channels[2]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[2]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[2].maxY));
assert.strictEqual(true, isInRange(stats.channels[2]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[2].maxY, 0, 2725));
done(); done();
}); });
@@ -474,52 +474,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 615101275)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 615101275));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 83061892917)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 83061892917));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 101.44954540768993)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 101.44954540768993));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 58.373870588815414)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 58.373870588815414));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 462824115)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 462824115));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 47083677255)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 47083677255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 76.33425255128337)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 76.33425255128337));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 44.03023262954866)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 44.03023262954866));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 372986756)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 372986756));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 32151543524)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 32151543524));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 61.51724663436759)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 61.51724663436759));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 38.96702865090125)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 38.96702865090125));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
}).catch(function (err) { }).catch(function (err) {
throw err; throw err;
}); });
@@ -531,52 +531,52 @@ describe('Image Stats', function () {
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel // red channel
assert.strictEqual(0, stats.channels[0]['min']); assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(255, stats.channels[0]['max']); assert.strictEqual(255, stats.channels[0].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['sum'], 615101275)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 615101275));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['squaresSum'], 83061892917)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 83061892917));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['mean'], 101.44954540768993)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 101.44954540768993));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0]['stdev'], 58.373870588815414)); assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 58.373870588815414));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// green channel // green channel
assert.strictEqual(0, stats.channels[1]['min']); assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(255, stats.channels[1]['max']); assert.strictEqual(255, stats.channels[1].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['sum'], 462824115)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 462824115));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['squaresSum'], 47083677255)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 47083677255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['mean'], 76.33425255128337)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 76.33425255128337));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1]['stdev'], 44.03023262954866)); assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 44.03023262954866));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
// blue channel // blue channel
assert.strictEqual(0, stats.channels[2]['min']); assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(255, stats.channels[2]['max']); assert.strictEqual(255, stats.channels[2].max);
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['sum'], 372986756)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 372986756));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['squaresSum'], 32151543524)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 32151543524));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['mean'], 61.51724663436759)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 61.51724663436759));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2]['stdev'], 38.96702865090125)); assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 38.96702865090125));
assert.strictEqual(true, isInteger(stats.channels[0]['minX'])); assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0]['minX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['minY'])); assert.strictEqual(true, isInteger(stats.channels[0].minY));
assert.strictEqual(true, isInRange(stats.channels[0]['minY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].minY, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxX'])); assert.strictEqual(true, isInteger(stats.channels[0].maxX));
assert.strictEqual(true, isInRange(stats.channels[0]['maxX'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxX, 0, 2725));
assert.strictEqual(true, isInteger(stats.channels[0]['maxY'])); assert.strictEqual(true, isInteger(stats.channels[0].maxY));
assert.strictEqual(true, isInRange(stats.channels[0]['maxY'], 0, 2725)); assert.strictEqual(true, isInRange(stats.channels[0].maxY, 0, 2725));
}).catch(function (err) { }).catch(function (err) {
throw err; throw err;
}); });

View File

@@ -2,6 +2,7 @@
const fs = require('fs'); const fs = require('fs');
const assert = require('assert'); const assert = require('assert');
const promisify = require('util').promisify;
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const sharp = require('../../'); const sharp = require('../../');
@@ -85,6 +86,19 @@ describe('TIFF', function () {
}); });
}); });
it('Increasing TIFF quality increases file size', () =>
sharp(fixtures.inputJpgWithLandscapeExif1)
.tiff({ quality: 40 })
.toBuffer()
.then(tiff40 => sharp(fixtures.inputJpgWithLandscapeExif1)
.tiff({ quality: 90 })
.toBuffer()
.then(tiff90 =>
assert.strictEqual(true, tiff40.length < tiff90.length)
)
)
);
it('Invalid TIFF quality throws error', function () { it('Invalid TIFF quality throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().tiff({ quality: 101 }); sharp().tiff({ quality: 101 });
@@ -137,40 +151,40 @@ describe('TIFF', function () {
}); });
}); });
it('TIFF setting xres and yres on file', function (done) { it('TIFF setting xres and yres on file', () =>
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
.tiff({ .tiff({
xres: (res), xres: 1000,
yres: (res) yres: 1000
}) })
.toFile(fixtures.outputTiff, (err, info) => { .toFile(fixtures.outputTiff)
if (err) throw err; .then(() => sharp(fixtures.outputTiff)
assert.strictEqual('tiff', info.format); .metadata()
sharp(fixtures.outputTiff).metadata(function (err, metadata) { .then(({ density }) => {
if (err) throw err; assert.strictEqual(true,
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi density === 2540 || // libvips <= 8.8.2
rimraf(fixtures.outputTiff, done); density === 25400); // libvips >= 8.8.3
}); return promisify(rimraf)(fixtures.outputTiff);
}); })
}); )
);
it('TIFF setting xres and yres on buffer', function (done) { it('TIFF setting xres and yres on buffer', () =>
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
.tiff({ .tiff({
xres: (res), xres: 1000,
yres: (res) yres: 1000
}) })
.toBuffer(function (err, data, info) { .toBuffer()
if (err) throw err; .then(data => sharp(data)
sharp(data).metadata(function (err, metadata) { .metadata()
if (err) throw err; .then(({ density }) => {
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi assert.strictEqual(true,
done(); density === 2540 || // libvips <= 8.8.2
}); density === 25400); // libvips >= 8.8.3
}); })
}); )
);
it('TIFF invalid xres value should throw an error', function () { it('TIFF invalid xres value should throw an error', function () {
assert.throws(function () { assert.throws(function () {

View File

@@ -56,7 +56,7 @@ const assertZoomifyTiles = function (directory, expectedTileSize, expectedLevels
fs.readdirSync(path.join(directory, 'TileGroup0')).forEach(function (tile) { fs.readdirSync(path.join(directory, 'TileGroup0')).forEach(function (tile) {
// Verify tile file name // Verify tile file name
assert.ok(/^[0-9]+-[0-9]+-[0-9]+\.jpg$/.test(tile)); assert.ok(/^[0-9]+-[0-9]+-[0-9]+\.jpg$/.test(tile));
let level = parseInt(tile.split('-')[0]); const level = parseInt(tile.split('-')[0]);
maxTileLevel = Math.max(maxTileLevel, level); maxTileLevel = Math.max(maxTileLevel, level);
}); });
@@ -91,6 +91,29 @@ const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels,
}); });
}; };
// Verifies tiles at specified level in a given output directory are > size+overlap
const assertTileOverlap = function (directory, tileSize, done) {
// Get sorted levels
const levels = fs.readdirSync(directory).sort((a, b) => a - b);
// Select the highest tile level
const highestLevel = levels[levels.length - 1];
// Get sorted tiles from greatest level
const tiles = fs.readdirSync(path.join(directory, highestLevel)).sort();
// Select a tile from the approximate center of the image
const squareTile = path.join(directory, highestLevel, tiles[Math.floor(tiles.length / 2)]);
sharp(squareTile).metadata(function (err, metadata) {
if (err) {
throw err;
} else {
// Tile with an overlap should be larger than original size
assert.strictEqual(true, metadata.width > tileSize);
assert.strictEqual(true, metadata.height > tileSize);
done();
}
});
};
describe('Tile', function () { describe('Tile', function () {
it('Valid size values pass', function () { it('Valid size values pass', function () {
[1, 8192].forEach(function (size) { [1, 8192].forEach(function (size) {
@@ -297,7 +320,9 @@ describe('Tile', function () {
assert.strictEqual(2225, info.height); assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
assert.strictEqual('undefined', typeof info.size); assert.strictEqual('undefined', typeof info.size);
assertDeepZoomTiles(directory, 512 + (2 * 16), 13, done); assertDeepZoomTiles(directory, 512 + (2 * 16), 13, function () {
assertTileOverlap(directory, 512, done);
});
}); });
}); });
}); });
@@ -319,7 +344,7 @@ describe('Tile', function () {
assert.strictEqual('undefined', typeof info.size); assert.strictEqual('undefined', typeof info.size);
assertDeepZoomTiles(directory, 512, 13, done); assertDeepZoomTiles(directory, 512, 13, done);
// Verifies tiles in 10th level are rotated // Verifies tiles in 10th level are rotated
let tile = path.join(directory, '10', '0_1.jpeg'); const tile = path.join(directory, '10', '0_1.jpeg');
// verify that the width and height correspond to the rotated image // verify that the width and height correspond to the rotated image
// expected are w=512 and h=170 for the 0_1.jpeg. // expected are w=512 and h=170 for the 0_1.jpeg.
// if a 0 angle is supplied to the .tile function // if a 0 angle is supplied to the .tile function
@@ -396,7 +421,7 @@ describe('Tile', function () {
if (err) throw err; if (err) throw err;
// assert them 0_0.jpeg doesn't exist because it's a white tile // assert them 0_0.jpeg doesn't exist because it's a white tile
const whiteTilePath = path.join(directory, '11', '0_0.jpeg'); const whiteTilePath = path.join(directory, '11', '0_0.jpeg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`); assert.strictEqual(fs.existsSync(whiteTilePath), false, 'Tile should not exist');
// Verify only one depth generated // Verify only one depth generated
assertDeepZoomTiles(directory, 256, 12, done); assertDeepZoomTiles(directory, 256, 12, done);
}); });
@@ -503,7 +528,7 @@ describe('Tile', function () {
if (err) throw err; if (err) throw err;
// assert them 0_0.jpeg doesn't exist because it's a white tile // assert them 0_0.jpeg doesn't exist because it's a white tile
const whiteTilePath = path.join(directory, 'TileGroup0', '2-0-0.jpg'); const whiteTilePath = path.join(directory, 'TileGroup0', '2-0-0.jpg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`); assert.strictEqual(fs.existsSync(whiteTilePath), false, 'Tile should not exist');
assert.strictEqual('dz', info.format); assert.strictEqual('dz', info.format);
assert.strictEqual(2048, info.width); assert.strictEqual(2048, info.width);
assert.strictEqual(1536, info.height); assert.strictEqual(1536, info.height);
@@ -727,7 +752,7 @@ describe('Tile', function () {
if (err) throw err; if (err) throw err;
const whiteTilePath = path.join(directory, '4', '8', '0.jpg'); const whiteTilePath = path.join(directory, '4', '8', '0.jpg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`); assert.strictEqual(fs.existsSync(whiteTilePath), false, 'Tile should not exist');
assert.strictEqual('dz', info.format); assert.strictEqual('dz', info.format);
assert.strictEqual(2809, info.width); assert.strictEqual(2809, info.width);

19
test/unit/toBuffer.js Normal file
View File

@@ -0,0 +1,19 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('toBuffer', () => {
it('reusing same sharp object does not reset previously passed parameters to toBuffer', (done) => {
const image = sharp(fixtures.inputJpg);
image.toBuffer({ resolveWithObject: true }).then((obj) => {
image.toBuffer().then((buff) => {
assert.strict.equal(Buffer.isBuffer(buff), true);
assert.strict.equal(typeof obj, 'object');
done();
});
});
});
});

View File

@@ -41,6 +41,21 @@ describe('Trim borders', function () {
}); });
}); });
it('single colour PNG where alpha channel provides the image', () =>
sharp(fixtures.inputPngImageInAlpha)
.trim()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
assert.strictEqual(true, data.length > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(916, info.width);
assert.strictEqual(137, info.height);
assert.strictEqual(4, info.channels);
assert.strictEqual(-6, info.trimOffsetLeft);
assert.strictEqual(-20, info.trimOffsetTop);
})
);
it('16-bit PNG with alpha channel', function (done) { it('16-bit PNG with alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency16bit) sharp(fixtures.inputPngWithTransparency16bit)
.resize(32, 32) .resize(32, 32)
@@ -58,6 +73,27 @@ describe('Trim borders', function () {
}); });
}); });
it('Attempt to trim 2x2 pixel image fails', function (done) {
sharp({
create: {
width: 2,
height: 2,
channels: 3,
background: 'red'
}
})
.trim()
.toBuffer()
.then(() => {
done(new Error('Expected an error'));
})
.catch(err => {
assert.strictEqual('Image to trim must be at least 3x3 pixels', err.message);
done();
})
.catch(done);
});
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 () {

View File

@@ -118,6 +118,13 @@ describe('Utilities', function () {
assert.strictEqual(true, sharp.format.raw[direction].stream); assert.strictEqual(true, sharp.format.raw[direction].stream);
}); });
}); });
it('vips format supports filesystem only', function () {
['input', 'output'].forEach(function (direction) {
assert.strictEqual(true, sharp.format.vips[direction].file);
assert.strictEqual(false, sharp.format.vips[direction].buffer);
assert.strictEqual(false, sharp.format.vips[direction].stream);
});
});
}); });
describe('Versions', function () { describe('Versions', function () {