Compare commits

..

19 Commits

Author SHA1 Message Date
Lovell Fuller
1ff84b20b7 Release v0.29.3 2021-11-14 11:40:19 +00:00
Lovell Fuller
97655d2dfd Bump deps 2021-11-14 09:17:44 +00:00
Michael B. Klein
d10d7b02d4 Docs: remove duplicate entry for mbklein (#2971) 2021-11-11 19:10:44 +00:00
Lovell Fuller
2ffdae2914 Docs: changelog and credit for #2952 2021-11-08 19:43:49 +00:00
Michael B. Klein
342de36973 Impute TIFF xres/yres from withMetadata({density}) 2021-11-08 19:43:42 +00:00
Lovell Fuller
b33231d4bd Ensure correct dimensions when contain 1px image #2951 2021-11-07 16:35:30 +00:00
Lovell Fuller
319db21f29 Release v0.29.2 2021-10-21 09:15:21 +01:00
Lovell Fuller
d359331426 Remove animation props from single page images #2890 2021-10-18 20:27:10 +01:00
Lovell Fuller
7ae151362b Bump devDeps 2021-10-17 15:17:50 +01:00
Lovell Fuller
648a1e05da Throw error rather than exit for invalid binaries #2931 2021-10-17 15:14:40 +01:00
Lovell Fuller
b9f211fe34 Docs: changelog for #2918 2021-10-17 15:11:38 +01:00
Dmitri Pyatkov
e475d9e47f Improve error message on Windows for version conflict (#2918) 2021-10-17 14:10:28 +01:00
Lovell Fuller
f37ca8249a Bump deps 2021-09-22 11:41:22 +01:00
Lovell Fuller
1dd4be670d Add timeout function to limit processing time 2021-09-22 10:33:59 +01:00
Lovell Fuller
197d4cf835 Docs: changelog and credit for #2893 2021-09-22 10:31:12 +01:00
Lovell Fuller
83eed86b53 Docs: clarify prebuilt libc support on ARMv6/v7 2021-09-22 10:08:52 +01:00
Lovell Fuller
bbf612cb9e Replace use of deprecated util.inherits 2021-09-22 10:08:44 +01:00
Erlend
2679bb567b Allow use of 'tif' to select TIFF output (#2893) 2021-09-16 18:49:14 +01:00
Lovell Fuller
481e350f39 Ensure 'versions' is populated from vendored libvips 2021-09-07 11:21:00 +01:00
19 changed files with 279 additions and 17 deletions

View File

@@ -379,6 +379,8 @@ Returns **Sharp**
Use these TIFF options for output image. Use these TIFF options for output image.
The `density` can be set in pixels/inch via [withMetadata][1] instead of providing `xres` and `yres` in pixels/mm.
### Parameters ### Parameters
* `options` **[Object][6]?** output options * `options` **[Object][6]?** output options
@@ -542,6 +544,26 @@ sharp('input.tiff')
Returns **Sharp** Returns **Sharp**
## timeout
Set a timeout for processing, in seconds.
Use a value of zero to continue processing indefinitely, the default behaviour.
The clock starts when libvips opens an input image for processing.
Time spent waiting for a libuv thread to become available is not included.
### Parameters
* `options` **[Object][6]**
* `options.seconds` **[number][9]** Number of seconds after which processing will be stopped
Returns **Sharp**
**Meta**
* **since**: 0.29.2
[1]: #withmetadata [1]: #withmetadata
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

View File

@@ -4,6 +4,35 @@
Requires libvips v8.11.3 Requires libvips v8.11.3
### v0.29.3 - 14th November 2021
* Ensure correct dimensions when containing image resized to 1px.
[#2951](https://github.com/lovell/sharp/issues/2951)
* Impute TIFF `xres`/`yres` from `density` provided to `withMetadata`.
[#2952](https://github.com/lovell/sharp/pull/2952)
[@mbklein](https://github.com/mbklein)
### v0.29.2 - 21st October 2021
* Add `timeout` function to limit processing time.
* Ensure `sharp.versions` is populated from vendored libvips.
* Remove animation properties from single page images.
[#2890](https://github.com/lovell/sharp/issues/2890)
* Allow use of 'tif' to select TIFF output.
[#2893](https://github.com/lovell/sharp/pull/2893)
[@erf](https://github.com/erf)
* Improve error message on Windows for version conflict.
[#2918](https://github.com/lovell/sharp/pull/2918)
[@dkrnl](https://github.com/dkrnl)
* Throw error rather than exit when invalid binaries detected.
[#2931](https://github.com/lovell/sharp/issues/2931)
### v0.29.1 - 7th September 2021 ### v0.29.1 - 7th September 2021
* Add `lightness` option to `modulate` operation. * Add `lightness` option to `modulate` operation.

View File

@@ -223,4 +223,7 @@ Name: Tenpi
GitHub: https://github.com/Tenpi GitHub: https://github.com/Tenpi
Name: Zaruike Name: Zaruike
https://github.com/Zaruike GitHub: https://github.com/Zaruike
Name: Erlend F
GitHub: https://github.com/erf

View File

@@ -31,8 +31,8 @@ JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp: The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28) * Linux ARMv7 (glibc >= 2.28)
* Linux ARMv6 (glibc >= 2.28)
* Windows ARM64 * Windows ARM64
The following platforms require compilation of both libvips and sharp from source: The following platforms require compilation of both libvips and sharp from source:
@@ -40,6 +40,8 @@ The following platforms require compilation of both libvips and sharp from sourc
* Linux x86 * Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6) * Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28) * Linux ARM64 (glibc <= 2.28)
* Linux ARMv7 (glibc <= 2.27, musl)
* Linux ARMv6 (glibc <= 2.27, musl)
* Linux PowerPC * Linux PowerPC
* FreeBSD * FreeBSD
* OpenBSD * OpenBSD

File diff suppressed because one or more lines are too long

View File

@@ -273,6 +273,7 @@ const Sharp = function (input, options) {
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
tileCentre: false, tileCentre: false,
tileId: 'https://example.com/iiif', tileId: 'https://example.com/iiif',
timeoutSeconds: 0,
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings
@@ -288,7 +289,8 @@ const Sharp = function (input, options) {
this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
return this; return this;
}; };
util.inherits(Sharp, stream.Duplex); Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
Object.setPrototypeOf(Sharp, stream.Duplex);
/** /**
* Take a "snapshot" of the Sharp instance, returning a new instance. * Take a "snapshot" of the Sharp instance, returning a new instance.

View File

@@ -13,6 +13,7 @@ const formats = new Map([
['png', 'png'], ['png', 'png'],
['raw', 'raw'], ['raw', 'raw'],
['tiff', 'tiff'], ['tiff', 'tiff'],
['tif', 'tiff'],
['webp', 'webp'], ['webp', 'webp'],
['gif', 'gif'], ['gif', 'gif'],
['jp2', 'jp2'], ['jp2', 'jp2'],
@@ -636,6 +637,8 @@ function trySetAnimationOptions (source, target) {
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
* *
* The `density` can be set in pixels/inch via {@link withMetadata} instead of providing `xres` and `yres` in pixels/mm.
*
* @example * @example
* // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
* sharp('input.svg') * sharp('input.svg')
@@ -974,6 +977,31 @@ function tile (options) {
return this._updateFormatOut('dz'); return this._updateFormatOut('dz');
} }
/**
* Set a timeout for processing, in seconds.
* Use a value of zero to continue processing indefinitely, the default behaviour.
*
* The clock starts when libvips opens an input image for processing.
* Time spent waiting for a libuv thread to become available is not included.
*
* @since 0.29.2
*
* @param {Object} options
* @param {number} options.seconds - Number of seconds after which processing will be stopped
* @returns {Sharp}
*/
function timeout (options) {
if (!is.plainObject(options)) {
throw is.invalidParameterError('options', 'object', options);
}
if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
this.options.timeoutSeconds = options.seconds;
} else {
throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
}
return this;
}
/** /**
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
* in which case revert to input format. * in which case revert to input format.
@@ -1128,6 +1156,7 @@ module.exports = function (Sharp) {
gif, gif,
raw, raw,
tile, tile,
timeout,
// Private // Private
_updateFormatOut, _updateFormatOut,
_setBooleanOption, _setBooleanOption,

View File

@@ -19,6 +19,13 @@ try {
help.push( help.push(
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install' '- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
); );
console.error(help.join('\n')); // Check loaded
process.exit(1); if (process.platform === 'win32') {
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
if (loadedModule) {
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
}
}
throw new Error(help.join('\n'));
} }

View File

@@ -4,6 +4,7 @@ const events = require('events');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
const is = require('./is'); const is = require('./is');
const platformAndArch = require('./platform')();
const sharp = require('./sharp'); const sharp = require('./sharp');
/** /**
@@ -45,7 +46,7 @@ let versions = {
vips: sharp.libvipsVersion() vips: sharp.libvipsVersion()
}; };
try { try {
versions = require(`../vendor/${versions.vips}/versions.json`); versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`);
} catch (err) {} } catch (err) {}
/** /**

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, AVIF and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.29.1", "version": "0.29.3",
"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": [
@@ -126,25 +126,25 @@
"dependencies": { "dependencies": {
"color": "^4.0.1", "color": "^4.0.1",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"node-addon-api": "^4.1.0", "node-addon-api": "^4.2.0",
"prebuild-install": "^6.1.4", "prebuild-install": "^7.0.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"simple-get": "^3.1.0", "simple-get": "^4.0.0",
"tar-fs": "^2.1.1", "tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.2.1", "async": "^3.2.2",
"cc": "^3.0.1", "cc": "^3.0.1",
"decompress-zip": "^0.3.3", "decompress-zip": "^0.3.3",
"documentation": "^13.2.5", "documentation": "^13.2.5",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^2.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^9.1.1", "mocha": "^9.1.3",
"mock-fs": "^5.0.0", "mock-fs": "^5.1.2",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prebuild": "^10.0.1", "prebuild": "^11.0.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semistandard": "^16.0.1" "semistandard": "^16.0.1"
}, },

View File

@@ -610,6 +610,33 @@ namespace sharp {
return warning; return warning;
} }
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const seconds) {
if (seconds > 0) {
VipsImage *im = image.get_image();
if (im->progress_signal == NULL) {
int *timeout = VIPS_NEW(im, int);
*timeout = seconds;
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
vips_image_set_progress(im, TRUE);
}
}
}
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
// printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
if (*timeout > 0 && progress->run >= *timeout) {
vips_image_set_kill(im, TRUE);
vips_error("timeout", "%d%% complete", progress->percent);
*timeout = 0;
}
}
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed. within the input image, applying the given gravity during an embed.

View File

@@ -250,6 +250,16 @@ namespace sharp {
*/ */
std::string VipsWarningPop(); std::string VipsWarningPop();
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const timeoutSeconds);
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);
/* /*
Calculate the (left, top) coordinates of the output image Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed. within the input image, applying the given gravity during an embed.

View File

@@ -289,6 +289,10 @@ class PipelineWorker : public Napi::AsyncWorker {
yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeHeight); yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeHeight);
} }
} }
// Remove animation properties from single page images
if (baton->input->pages == 1) {
image = sharp::RemoveAnimationProperties(image);
}
// Ensure we're using a device-independent colour space // Ensure we're using a device-independent colour space
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb"; char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
@@ -378,11 +382,15 @@ class PipelineWorker : public Napi::AsyncWorker {
// Ensure shortest edge is at least 1 pixel // Ensure shortest edge is at least 1 pixel
if (image.width() / xfactor < 0.5) { if (image.width() / xfactor < 0.5) {
xfactor = 2 * image.width(); xfactor = 2 * image.width();
baton->width = 1; if (baton->canvas != Canvas::EMBED) {
baton->width = 1;
}
} }
if (image.height() / yfactor < 0.5) { if (image.height() / yfactor < 0.5) {
yfactor = 2 * image.height(); yfactor = 2 * image.height();
baton->height = 1; if (baton->canvas != Canvas::EMBED) {
baton->height = 1;
}
} }
image = image.resize(1.0 / xfactor, VImage::option() image = image.resize(1.0 / xfactor, VImage::option()
->set("vscale", 1.0 / yfactor) ->set("vscale", 1.0 / yfactor)
@@ -764,6 +772,7 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->loop); baton->loop);
// Output // Output
sharp::SetTimeout(image, baton->timeoutSeconds);
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
@@ -1451,6 +1460,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
std::string k = sharp::AttrAsStr(mdStrKeys, i); std::string k = sharp::AttrAsStr(mdStrKeys, i);
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k))); baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
} }
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
// Format-specific // Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@@ -1486,6 +1496,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
}
// tiff compression options // tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>( baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,

View File

@@ -182,6 +182,7 @@ struct PipelineBaton {
double withMetadataDensity; double withMetadataDensity;
std::string withMetadataIcc; std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs; std::unordered_map<std::string, std::string> withMetadataStrs;
int timeoutSeconds;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
@@ -315,6 +316,7 @@ struct PipelineBaton {
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
withMetadataDensity(0.0), withMetadataDensity(0.0),
timeoutSeconds(0),
convKernelWidth(0), convKernelWidth(0),
convKernelHeight(0), convKernelHeight(0),
convKernelScale(0.0), convKernelScale(0.0),

View File

@@ -298,6 +298,21 @@ describe('Input/output', function () {
}); });
}); });
it('Support output to tif format', function (done) {
sharp(fixtures.inputTiff)
.resize(320, 240)
.toFormat('tif')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Fail when output File is input File', function (done) { it('Fail when output File is input File', function (done) {
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) { sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
assert(err instanceof Error); assert(err instanceof Error);

View File

@@ -605,6 +605,40 @@ describe('Resize dimensions', function () {
}); });
}); });
it('Ensure embedded shortest edge (height) is at least 1 pixel', function () {
return sharp({
create: {
width: 200,
height: 1,
channels: 3,
background: 'red'
}
})
.resize({ width: 50, height: 50, fit: sharp.fit.contain })
.toBuffer({ resolveWithObject: true })
.then(function (output) {
assert.strictEqual(50, output.info.width);
assert.strictEqual(50, output.info.height);
});
});
it('Ensure embedded shortest edge (width) is at least 1 pixel', function () {
return sharp({
create: {
width: 1,
height: 200,
channels: 3,
background: 'red'
}
})
.resize({ width: 50, height: 50, fit: sharp.fit.contain })
.toBuffer({ resolveWithObject: true })
.then(function (output) {
assert.strictEqual(50, output.info.width);
assert.strictEqual(50, output.info.height);
});
});
it('Skip shrink-on-load where one dimension <4px', async () => { it('Skip shrink-on-load where one dimension <4px', async () => {
const jpeg = await sharp({ const jpeg = await sharp({
create: { create: {

View File

@@ -188,6 +188,26 @@ describe('TIFF', function () {
) )
); );
it('TIFF imputes xres and yres from withMetadataDensity if not explicitly provided', async () => {
const data = await sharp(fixtures.inputTiff)
.resize(8, 8)
.tiff()
.withMetadata({ density: 600 })
.toBuffer();
const { density } = await sharp(data).metadata();
assert.strictEqual(600, density);
});
it('TIFF uses xres and yres over withMetadataDensity if explicitly provided', async () => {
const data = await sharp(fixtures.inputTiff)
.resize(8, 8)
.tiff({ xres: 1000, yres: 1000 })
.withMetadata({ density: 600 })
.toBuffer();
const { density } = await sharp(data).metadata();
assert.strictEqual(25400, density);
});
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 () {
sharp().tiff({ xres: '1000.0' }); sharp().tiff({ xres: '1000.0' });

26
test/unit/timeout.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Timeout', function () {
it('Will timeout after 1s when performing slow blur operation', () => assert.rejects(
() => sharp(fixtures.inputJpg)
.blur(100)
.timeout({ seconds: 1 })
.toBuffer(),
/timeout: [0-9]+% complete/
));
it('invalid object', () => assert.throws(
() => sharp().timeout('fail'),
/Expected object for options but received fail of type string/
));
it('invalid seconds', () => assert.throws(
() => sharp().timeout({ seconds: 'fail' }),
/Expected integer between 0 and 3600 for seconds but received fail of type string/
));
});

View File

@@ -209,4 +209,24 @@ describe('WebP', function () {
fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done); fixtures.assertSimilar(fixtures.inputWebPAnimated, data, done);
}); });
}); });
it('should remove animation properties when loading single page', async () => {
const data = await sharp(fixtures.inputGifAnimatedLoop3)
.resize({ height: 570 })
.webp({ reductionEffort: 0 })
.toBuffer();
const metadata = await sharp(data).metadata();
assert.deepStrictEqual(metadata, {
format: 'webp',
size: 2580,
width: 740,
height: 570,
space: 'srgb',
channels: 3,
depth: 'uchar',
isProgressive: false,
hasProfile: false,
hasAlpha: false
});
});
}); });