Compare commits

..

22 Commits

Author SHA1 Message Date
Lovell Fuller
fbe48d75dd Release v0.26.2 2020-10-14 17:59:55 +01:00
Lovell Fuller
20ba0f49dd Changelog entry and doc refresh for #2397 2020-10-08 10:05:39 +01:00
beig
c213e9878d Add centre/center option to tile-based output (#2397) 2020-10-08 09:51:54 +01:00
Lovell Fuller
9704ca4c18 Add support for libvips compiled with OpenEXR #698 2020-10-07 15:12:06 +01:00
Lovell Fuller
49dce6219e Docs: changelog entry for #2379 2020-10-07 14:43:13 +01:00
Adam Coster
260ff6c94f Docs: clarify response object description for trim (#2400) 2020-10-06 17:08:32 +01:00
Adam Lovatt
3ec281d104 Ensure support for yarn v2 (#2379) 2020-09-23 21:41:14 +01:00
Lovell Fuller
c4c43d525b Release v0.26.1 2020-09-20 09:29:50 +01:00
Lovell Fuller
6c5cde363a Ensure animated GIF output is optimised #2376 2020-09-19 20:53:15 +01:00
Lovell Fuller
d46b4d950f Allow spaces in installation directory #2279
Uses new include_dir property of node-addon-api
2020-09-19 14:26:30 +01:00
Lovell Fuller
b369c4bb8a Ensure stats can be calculated for 1x1 input #2372 2020-09-17 11:08:52 +01:00
Lovell Fuller
c3898487c4 CI: name the FreeBSD task 2020-09-17 11:02:16 +01:00
Lovell Fuller
ca3c5b400f CI: install ARM64 nodejs package with dependency on python3-minimal
Node.js 14 will work but 10 and 12 will fail until
https://github.com/nodesource/distributions/issues/1100 is fixed
2020-09-15 19:21:32 +01:00
Lovell Fuller
97772b176c Docs: changelog entry for #2369 2020-09-15 18:05:22 +01:00
AcrylicShrimp
08a2965f1c Ensure animation-related properties work with stream-based input 2020-09-15 15:12:26 +01:00
Lovell Fuller
76dcddfa3d Changelog, credit, doc update for #2348 2020-09-02 09:06:15 +01:00
stefanprobst
79f476ae4d Allow input density range up to 100000 DPI (#2348) 2020-09-02 08:56:12 +01:00
Lovell Fuller
d406cb619c Improve error messaging for unsupported Node.js versions 2020-08-28 21:39:11 +01:00
Lovell Fuller
4f3890f1e4 Issue template: ask about installation architecture and platform 2020-08-28 13:59:44 +01:00
Lovell Fuller
7a9d58cc51 Docs: changelog entry for #2343 2020-08-28 13:56:35 +01:00
Lovell Fuller
eef87da0e1 Docs: CSP allow inline JS 2020-08-28 13:53:19 +01:00
Denis Soldatov
00e65f6f14 Ensure correct pageHeight when verifying image dimensions (#2343) 2020-08-28 13:39:19 +01:00
34 changed files with 258 additions and 33 deletions

View File

@@ -2,6 +2,7 @@ freebsd_instance:
image_family: freebsd-13-0-snap
task:
name: FreeBSD 13.0
env:
IGNORE_OSVERSION: yes
prerequisites_script:

View File

@@ -42,10 +42,6 @@ You deserve to add your details to the [list of contributors](https://github.com
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
| Release | WIP branch |
| ------: | :--------- |
| v0.26.0 | zoom |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### Add a new public method

View File

@@ -7,7 +7,7 @@ labels: installation
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime?
Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?

View File

@@ -72,7 +72,7 @@ jobs:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
@@ -86,7 +86,7 @@ jobs:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
@@ -100,7 +100,7 @@ jobs:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_14.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"

View File

@@ -95,7 +95,7 @@
'src/sharp.cc'
],
'include_dirs': [
'<!@(node -p "require(\'node-addon-api\').include")',
'<!(node -p "require(\'node-addon-api\').include_dir")',
],
'conditions': [
['use_global_libvips == "true"', {

View File

@@ -25,7 +25,7 @@ Implements the [stream.Duplex][1] class.
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
- `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
- `options.density` **[number][6]** number representing the DPI for vector images. (optional, default `72`)
- `options.density` **[number][6]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)

View File

@@ -372,6 +372,8 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
- `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
- `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
### Examples

View File

@@ -209,7 +209,8 @@ Returns **Sharp**
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, obtained from callback of `.toFile()` or `.toBuffer()`,
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters

View File

@@ -4,6 +4,39 @@
Requires libvips v8.10.0
### v0.26.2 - 14th October 2020
* Add support for EXR input. Requires libvips compiled with OpenEXR.
[#698](https://github.com/lovell/sharp/issues/698)
* Ensure support for yarn v2.
[#2379](https://github.com/lovell/sharp/pull/2379)
[@jalovatt](https://github.com/jalovatt)
* Add centre/center option to tile-based output.
[#2397](https://github.com/lovell/sharp/pull/2397)
[@beig](https://github.com/beig)
### v0.26.1 - 20th September 2020
* Ensure correct pageHeight when verifying multi-page image dimensions.
[#2343](https://github.com/lovell/sharp/pull/2343)
[@derom](https://github.com/derom)
* Allow input density range up to 100000 DPI.
[#2348](https://github.com/lovell/sharp/pull/2348)
[@stefanprobst](https://github.com/stefanprobst)
* Ensure animation-related properties can be set for Stream-based input.
[#2369](https://github.com/lovell/sharp/pull/2369)
[@AcrylicShrimp](https://github.com/AcrylicShrimp)
* Ensure `stats` can be calculated for 1x1 input.
[#2372](https://github.com/lovell/sharp/issues/2372)
* Ensure animated GIF output is optimised.
[#2376](https://github.com/lovell/sharp/issues/2376)
### v0.26.0 - 25th August 2020
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.

View File

@@ -197,3 +197,12 @@ GitHub: https://github.com/deftomat
Name: Robert O'Rourke
GitHub: https://github.com/roborourke
Name: Denis Soldatov
GitHub: https://github.com/derom
Name: Stefan Probst
GitHub: https://github.com/stefanprobst
Name: Thomas Beiganz
GitHub: https://github.com/beig

View File

@@ -8,7 +8,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
connect-src 'self' https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
script-src 'sha256-iXDHv+t2aGdJcmEvwHfqmGH4SrOSx+P8rCF6+WLLwXA=' 'unsafe-eval'
script-src 'unsafe-inline' 'unsafe-eval'
https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js
https://www.google-analytics.com/analytics.js;">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
@@ -19,7 +19,7 @@
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.0/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.2/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
<link rel="dns-prefetch" href="https://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
@@ -139,7 +139,7 @@
docuteApiTitlePlugin,
docuteApiSearchPlugin
],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.0/docs',
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.2/docs',
nav: [
{
title: 'Funding',

View File

@@ -80,6 +80,12 @@ try {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);

View File

@@ -102,7 +102,7 @@ const debuglog = util.debuglog('sharp');
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
* This can reduce memory usage and might improve performance on some systems.
* @param {number} [options.density=72] - number representing the DPI for vector images.
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
@@ -238,6 +238,7 @@ const Sharp = function (input, options) {
tileAngle: 0,
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
tileCentre: false,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings

View File

@@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError }
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
: undefined;
}
@@ -57,10 +57,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}
// Density
if (is.defined(inputOptions.density)) {
if (is.inRange(inputOptions.density, 1, 2400)) {
if (is.inRange(inputOptions.density, 1, 100000)) {
inputDescriptor.density = inputOptions.density;
} else {
throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
}
}
// limitInputPixels

View File

@@ -658,6 +658,8 @@ function raw () {
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @param {boolean} [options.centre=false] centre image in tile.
* @param {boolean} [options.center=false] alternative spelling of centre.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -726,6 +728,11 @@ function tile (options) {
} else if (is.defined(options.layout) && options.layout === 'google') {
this.options.tileSkipBlanks = 5;
}
// Center image in tile
const centre = is.bool(options.center) ? options.center : options.centre;
if (is.defined(centre)) {
this._setBooleanOption('tileCentre', centre);
}
}
// Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {

View File

@@ -386,7 +386,8 @@ function extract (options) {
* 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, obtained from callback of `.toFile()` or `.toBuffer()`,
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
*
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp}

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.26.0",
"version": "0.26.2",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -114,7 +114,7 @@
"dependencies": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.0.0",
"node-addon-api": "^3.0.2",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.5",
"semver": "^7.3.2",

View File

@@ -180,6 +180,7 @@ namespace sharp {
case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break;
case ImageType::EXR: id = "exr"; break;
case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
@@ -210,6 +211,7 @@ namespace sharp {
{ "openslideload", ImageType::OPENSLIDE },
{ "ppmload", ImageType::PPM },
{ "fitsload", ImageType::FITS },
{ "openexrload", ImageType::EXR },
{ "vipsload", ImageType::VIPS },
{ "rawload", ImageType::RAW }
};
@@ -485,8 +487,8 @@ namespace sharp {
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
const int height = image.get_typeof("pageHeight") == G_TYPE_INT
? image.get_int("pageHeight")
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
? image.get_int(VIPS_META_PAGE_HEIGHT)
: image.height();
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || height > 65535) {

View File

@@ -111,6 +111,7 @@ namespace sharp {
OPENSLIDE,
PPM,
FITS,
EXR,
VIPS,
RAW,
UNKNOWN,

View File

@@ -782,6 +782,8 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif")));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@@ -925,6 +927,8 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif"));
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@@ -1010,6 +1014,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("centre", baton->tileCentre)
->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice
@@ -1399,6 +1404,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tileDepth = static_cast<VipsForeignDzDepth>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
sharp::AttrAsStr(options, "tileDepth").data()));
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
// Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {

View File

@@ -79,6 +79,7 @@ struct PipelineBaton {
int cropOffsetLeft;
int cropOffsetTop;
bool premultiplied;
bool tileCentre;
std::string kernel;
bool fastShrinkOnLoad;
double tintA;

View File

@@ -80,12 +80,14 @@ class StatsWorker : public Napi::AsyncWorker {
// Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
// Estimate sharpness via standard deviation of greyscale laplacian
VImage laplacian = VImage::new_matrixv(3, 3,
0.0, 1.0, 0.0,
1.0, -4.0, 1.0,
0.0, 1.0, 0.0);
laplacian.set("scale", 9.0);
baton->sharpness = greyscale.conv(laplacian).deviate();
if (image.width() > 1 || image.height() > 1) {
VImage laplacian = VImage::new_matrixv(3, 3,
0.0, 1.0, 0.0,
1.0, -4.0, 1.0,
0.0, 1.0, 0.0);
laplacian.set("scale", 9.0);
baton->sharpness = greyscale.conv(laplacian).deviate();
}
// Most dominant sRGB colour via 4096-bin 3D histogram
vips::VImage hist = sharp::RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_sRGB)

BIN
test/fixtures/big-height.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

3
test/fixtures/circle.svg vendored Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
<circle r="3.75" cx="4" cy="4" fill="deeppink" />
</svg>

After

Width:  |  Height:  |  Size: 122 B

BIN
test/fixtures/expected/circle.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
test/fixtures/expected/tile_centered.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -95,6 +95,7 @@ module.exports = {
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedBigHeight: getPath('big-height.webp'),
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
@@ -106,6 +107,7 @@ module.exports = {
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
inputGifAnimatedLoop3: getPath('animated-loop-3.gif'), // CC-BY-SA-4.0 Petrus3743 https://commons.wikimedia.org/wiki/File:01-Goldener_Schnitt_Formel-Animation.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputJPGBig: getPath('flowers.jpeg'),

View File

@@ -98,4 +98,36 @@ describe('GIF input', () => {
sharp().gif({ delay: [65536] });
});
});
it('should work with streams when only animated is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
});
it('should work with streams when only pages is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ pages: -1 }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
});
});

View File

@@ -302,7 +302,7 @@ describe('Input/output', function () {
});
it('Fail when input is empty Buffer', function (done) {
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.0+
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.1+
sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false);
done();
@@ -647,7 +647,7 @@ describe('Input/output', function () {
it('Invalid density: string', function () {
assert.throws(function () {
sharp({ density: 'zoinks' });
}, /Expected number between 1 and 2400 for density but received zoinks of type string/);
}, /Expected number between 1 and 100000 for density but received zoinks of type string/);
});
it('Setting animated property updates pages property', function () {
assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);

View File

@@ -462,6 +462,19 @@ describe('Image Stats', function () {
})
);
it('Entropy and sharpness of 1x1 input are zero', async () => {
const { entropy, sharpness } = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: 'red'
}
}).stats();
assert.strictEqual(entropy, 0);
assert.strictEqual(sharpness, 0);
});
it('Stream in, Callback out', function (done) {
const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().stats(function (err, stats) {

View File

@@ -48,6 +48,31 @@ describe('SVG input', function () {
});
});
it('Convert SVG to PNG at DPI larger than 2400', function (done) {
const size = 1024;
sharp(fixtures.inputSvgSmallViewBox).metadata(function (err, metadata) {
if (err) throw err;
const density = (size / Math.max(metadata.width, metadata.height)) * metadata.density;
sharp(fixtures.inputSvgSmallViewBox, { density })
.resize(size)
.toFormat('png')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(size, info.width);
assert.strictEqual(size, info.height);
fixtures.assertSimilar(fixtures.expected('circle.png'), data, function (err) {
if (err) throw err;
sharp(data).metadata(function (err, info) {
if (err) throw err;
assert.strictEqual(9216, info.density);
done();
});
});
});
});
});
it('Convert SVG to PNG at 14.4DPI', function (done) {
sharp(fixtures.inputSvg, { density: 14.4 })
.toFormat('png')

View File

@@ -289,6 +289,14 @@ describe('Tile', function () {
});
});
it('Invalid center parameter value fail', function () {
assert.throws(function () {
sharp().tile({
centre: 'true'
});
});
});
it('Deep Zoom layout', function (done) {
const directory = fixtures.path('output.dzi_files');
rimraf(directory, function () {
@@ -765,6 +773,46 @@ describe('Tile', function () {
});
});
it('Google layout with center image in tile', function (done) {
const directory = fixtures.path('output.google_center.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
center: true,
layout: 'google'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
fixtures.assertSimilar(fixtures.expected('tile_centered.jpg'), fs.readFileSync(path.join(directory, '0', '0', '0.jpg')), done);
});
});
});
it('Google layout with center image in tile centre', function (done) {
const directory = fixtures.path('output.google_center.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
centre: true,
layout: 'google'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
fixtures.assertSimilar(fixtures.expected('tile_centered.jpg'), fs.readFileSync(path.join(directory, '0', '0', '0.jpg')), done);
});
});
});
it('IIIF layout', function (done) {
const directory = fixtures.path('output.iiif.info');
rimraf(directory, function () {

View File

@@ -16,4 +16,12 @@ describe('toBuffer', () => {
});
});
});
it('correctly process animated webp with height > 16383', (done) => {
const image = sharp(fixtures.inputWebPAnimatedBigHeight, { animated: true });
image.toBuffer().then((buff) => {
assert.strictEqual(Buffer.isBuffer(buff), true);
done();
});
});
});

View File

@@ -1,5 +1,6 @@
'use strict';
const fs = require('fs');
const assert = require('assert');
const sharp = require('../../');
@@ -184,4 +185,28 @@ describe('WebP', function () {
assert.deepStrictEqual(updated.delay, expectedDelay);
});
it('should work with streams when only animated is set', function (done) {
fs.createReadStream(fixtures.inputWebPAnimated)
.pipe(sharp({ animated: true }))
.webp({ lossless: true })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done);
});
});
it('should work with streams when only pages is set', function (done) {
fs.createReadStream(fixtures.inputWebPAnimated)
.pipe(sharp({ pages: -1 }))
.webp({ lossless: true })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done);
});
});
});