mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add support for GIF output using cgif in prebuilt binaries
This commit is contained in:
parent
b876abaf88
commit
f7f3e43490
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
|
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF 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
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
smaller, web-friendly JPEG, PNG, AVIF and WebP images of varying dimensions.
|
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF 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
|
||||||
@ -21,9 +21,9 @@ do not require any additional install or runtime dependencies.
|
|||||||
|
|
||||||
### Formats
|
### Formats
|
||||||
|
|
||||||
This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG images.
|
This module supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF and SVG images.
|
||||||
|
|
||||||
Output images can be in JPEG, PNG, WebP, AVIF and TIFF formats as well as uncompressed raw pixel data.
|
Output images can be in JPEG, PNG, WebP, GIF, AVIF and TIFF formats as well as uncompressed raw pixel data.
|
||||||
|
|
||||||
Streams, Buffer objects and the filesystem can be used for input and output.
|
Streams, Buffer objects and the filesystem can be used for input and output.
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ Requires libvips v8.12.0
|
|||||||
|
|
||||||
### v0.30.0 - TBD
|
### v0.30.0 - TBD
|
||||||
|
|
||||||
|
* Add support for GIF output to prebuilt binaries.
|
||||||
|
|
||||||
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
|
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
|
||||||
|
|
||||||
* Properly emit close events for duplex streams.
|
* Properly emit close events for duplex streams.
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions">
|
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
|
<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;
|
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
|
||||||
connect-src 'self' https://www.google-analytics.com;
|
connect-src 'self' https://www.google-analytics.com;
|
||||||
|
@ -19,7 +19,7 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
|
|||||||
* macOS x64 (>= 10.13)
|
* macOS x64 (>= 10.13)
|
||||||
* macOS ARM64
|
* macOS ARM64
|
||||||
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
|
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
|
||||||
* Linux ARM64 (glibc >= 2.29, musl >= 1.1.24)
|
* Linux ARM64 (glibc >= 2.17, musl >= 1.1.24)
|
||||||
* Windows x64
|
* Windows x64
|
||||||
* Windows x86
|
* Windows x86
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ An ~7MB tarball containing libvips and its most commonly used dependencies
|
|||||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||||
|
|
||||||
This provides support for the
|
This provides support for the
|
||||||
JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
|
JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG (input) image formats.
|
||||||
|
|
||||||
The following platforms have prebuilt libvips but not sharp:
|
The following platforms have prebuilt libvips but not sharp:
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ The following platforms have prebuilt libvips but not sharp:
|
|||||||
The following platforms require compilation of both libvips and sharp from source:
|
The following platforms require compilation of both libvips and sharp from source:
|
||||||
|
|
||||||
* Linux x86
|
* Linux x86
|
||||||
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
|
|
||||||
* Linux ARM64 (glibc <= 2.28)
|
|
||||||
* Linux ARMv7 (glibc <= 2.27, musl)
|
* Linux ARMv7 (glibc <= 2.27, musl)
|
||||||
* Linux ARMv6 (glibc <= 2.27, musl)
|
* Linux ARMv6 (glibc <= 2.27, musl)
|
||||||
* Linux PowerPC
|
* Linux PowerPC
|
||||||
|
@ -13,7 +13,7 @@ const debuglog = util.debuglog('sharp');
|
|||||||
/**
|
/**
|
||||||
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
||||||
*
|
*
|
||||||
* JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
|
* JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
|
||||||
* When using Stream based output, derived attributes are available from the `info` event.
|
* When using Stream based output, derived attributes are available from the `info` event.
|
||||||
*
|
*
|
||||||
* Non-critical problems encountered during processing are emitted as `warning` events.
|
* Non-critical problems encountered during processing are emitted as `warning` events.
|
||||||
@ -246,6 +246,9 @@ const Sharp = function (input, options) {
|
|||||||
webpNearLossless: false,
|
webpNearLossless: false,
|
||||||
webpSmartSubsample: false,
|
webpSmartSubsample: false,
|
||||||
webpReductionEffort: 4,
|
webpReductionEffort: 4,
|
||||||
|
gifBitdepth: 8,
|
||||||
|
gifEffort: 7,
|
||||||
|
gifDither: 1,
|
||||||
tiffQuality: 80,
|
tiffQuality: 80,
|
||||||
tiffCompression: 'jpeg',
|
tiffCompression: 'jpeg',
|
||||||
tiffPredictor: 'horizontal',
|
tiffPredictor: 'horizontal',
|
||||||
|
@ -22,14 +22,15 @@ const formats = new Map([
|
|||||||
['j2c', 'jp2']
|
['j2c', 'jp2']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
|
||||||
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
||||||
|
|
||||||
|
const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write output image data to a file.
|
* Write output image data to a file.
|
||||||
*
|
*
|
||||||
* If an explicit output format is not selected, it will be inferred from the extension,
|
* If an explicit output format is not selected, it will be inferred from the extension,
|
||||||
* with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
|
* with JPEG, PNG, WebP, AVIF, TIFF, GIF, 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.
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
@ -63,8 +64,6 @@ function toFile (fileOut, callback) {
|
|||||||
err = new Error('Missing output file path');
|
err = new Error('Missing output file path');
|
||||||
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
||||||
err = new Error('Cannot use same file for input and output');
|
err = new Error('Cannot use same file for input and output');
|
||||||
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
|
|
||||||
err = errMagickSave;
|
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
if (is.fn(callback)) {
|
if (is.fn(callback)) {
|
||||||
@ -81,9 +80,9 @@ function toFile (fileOut, callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Write output to a Buffer.
|
* Write output to a Buffer.
|
||||||
* JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
|
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
|
||||||
*
|
*
|
||||||
* If no explicit format is set, the output 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 SVG input which becomes PNG output.
|
||||||
*
|
*
|
||||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
* See {@link withMetadata} for control over this.
|
* See {@link withMetadata} for control over this.
|
||||||
@ -412,7 +411,7 @@ function png (options) {
|
|||||||
const colours = options.colours || options.colors;
|
const colours = options.colours || options.colors;
|
||||||
if (is.defined(colours)) {
|
if (is.defined(colours)) {
|
||||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||||
this.options.pngBitdepth = 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
this.options.pngBitdepth = bitdepthFromColourCount(colours);
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||||
}
|
}
|
||||||
@ -495,13 +494,40 @@ function webp (options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use these GIF options for output image.
|
* Use these GIF options for the output image.
|
||||||
*
|
*
|
||||||
* Requires libvips compiled with support for ImageMagick or GraphicsMagick.
|
* The first entry in the palette is reserved for transparency.
|
||||||
* The prebuilt binaries do not include this - see
|
*
|
||||||
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
* @example
|
||||||
|
* // Convert PNG to GIF
|
||||||
|
* await sharp(pngBuffer)
|
||||||
|
* .gif()
|
||||||
|
* .toBuffer());
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert animated WebP to animated GIF
|
||||||
|
* await sharp('animated.webp', { animated: true })
|
||||||
|
* .toFile('animated.gif');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Create 128x128, non-dithered thumbnail of an animated GIF
|
||||||
|
* const { pages } = await sharp('animated.gif').metadata();
|
||||||
|
* const gif = await sharp('animated.gif', { animated: true })
|
||||||
|
* .resize({
|
||||||
|
* width: 128,
|
||||||
|
* height: 128 * pages
|
||||||
|
* })
|
||||||
|
* .gif({
|
||||||
|
* pageHeight: 128,
|
||||||
|
* dither: 0
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
*
|
*
|
||||||
* @param {Object} [options] - output options
|
* @param {Object} [options] - output options
|
||||||
|
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
|
||||||
|
* @param {number} [options.colors=256] - alternative spelling of `options.colours`
|
||||||
|
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
|
||||||
|
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
|
||||||
* @param {number} [options.pageHeight] - page height for animated output
|
* @param {number} [options.pageHeight] - page height for animated output
|
||||||
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
||||||
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
|
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
|
||||||
@ -509,10 +535,30 @@ function webp (options) {
|
|||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
/* istanbul ignore next */
|
|
||||||
function gif (options) {
|
function gif (options) {
|
||||||
if (!this.constructor.format.magick.output.buffer) {
|
if (is.object(options)) {
|
||||||
throw errMagickSave;
|
const colours = options.colours || options.colors;
|
||||||
|
if (is.defined(colours)) {
|
||||||
|
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||||
|
this.options.gifBitdepth = bitdepthFromColourCount(colours);
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.effort)) {
|
||||||
|
if (is.number(options.effort) && is.inRange(options.effort, 1, 10)) {
|
||||||
|
this.options.gifEffort = options.effort;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.dither)) {
|
||||||
|
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
|
||||||
|
this.options.gifDither = options.dither;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trySetAnimationOptions(options, this.options);
|
trySetAnimationOptions(options, this.options);
|
||||||
return this._updateFormatOut('gif', options);
|
return this._updateFormatOut('gif', options);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"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, GIF, AVIF and TIFF images",
|
||||||
"version": "0.29.3",
|
"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",
|
||||||
|
@ -762,9 +762,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
baton->width = image.width();
|
baton->width = image.width();
|
||||||
baton->height = image.height();
|
baton->height = image.height();
|
||||||
|
|
||||||
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
|
|
||||||
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
|
|
||||||
|
|
||||||
image = sharp::SetAnimationProperties(
|
image = sharp::SetAnimationProperties(
|
||||||
image,
|
image,
|
||||||
baton->pageHeight,
|
baton->pageHeight,
|
||||||
@ -817,8 +814,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
vips_area_unref(area);
|
vips_area_unref(area);
|
||||||
baton->formatOut = "jp2";
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
|
||||||
// Write PNG to buffer
|
// Write PNG to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
||||||
@ -853,14 +849,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
vips_area_unref(area);
|
vips_area_unref(area);
|
||||||
baton->formatOut = "webp";
|
baton->formatOut = "webp";
|
||||||
} else if (baton->formatOut == "gif" ||
|
} else if (baton->formatOut == "gif" ||
|
||||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
|
||||||
// Write GIF to buffer
|
// Write GIF to buffer
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("optimize_gif_frames", TRUE)
|
->set("bitdepth", baton->gifBitdepth)
|
||||||
->set("optimize_gif_transparency", TRUE)
|
->set("effort", baton->gifEffort)
|
||||||
->set("format", "gif")));
|
->set("dither", baton->gifDither)));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
area->free_fn = nullptr;
|
area->free_fn = nullptr;
|
||||||
@ -987,8 +983,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("tile_width", baton->jp2TileWidth));
|
->set("tile_width", baton->jp2TileWidth));
|
||||||
baton->formatOut = "jp2";
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
|
||||||
// Write PNG to file
|
// Write PNG to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||||
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
@ -1015,14 +1010,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("alpha_q", baton->webpAlphaQuality));
|
->set("alpha_q", baton->webpAlphaQuality));
|
||||||
baton->formatOut = "webp";
|
baton->formatOut = "webp";
|
||||||
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
|
(willMatchInput && inputImageType == sharp::ImageType::GIF)) {
|
||||||
// Write GIF to file
|
// Write GIF to file
|
||||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||||
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("optimize_gif_frames", TRUE)
|
->set("bitdepth", baton->gifBitdepth)
|
||||||
->set("optimize_gif_transparency", TRUE)
|
->set("effort", baton->gifEffort)
|
||||||
->set("format", "gif"));
|
->set("dither", baton->gifDither));
|
||||||
baton->formatOut = "gif";
|
baton->formatOut = "gif";
|
||||||
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
|
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
|
||||||
@ -1488,6 +1483,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
||||||
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
||||||
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
|
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
|
||||||
|
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
||||||
|
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
||||||
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
||||||
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
||||||
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
||||||
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
||||||
|
@ -160,6 +160,9 @@ struct PipelineBaton {
|
|||||||
bool webpLossless;
|
bool webpLossless;
|
||||||
bool webpSmartSubsample;
|
bool webpSmartSubsample;
|
||||||
int webpReductionEffort;
|
int webpReductionEffort;
|
||||||
|
int gifBitdepth;
|
||||||
|
int gifEffort;
|
||||||
|
double gifDither;
|
||||||
int tiffQuality;
|
int tiffQuality;
|
||||||
VipsForeignTiffCompression tiffCompression;
|
VipsForeignTiffCompression tiffCompression;
|
||||||
VipsForeignTiffPredictor tiffPredictor;
|
VipsForeignTiffPredictor tiffPredictor;
|
||||||
|
@ -8,7 +8,7 @@ fi
|
|||||||
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
||||||
|
|
||||||
for test in ./test/unit/*.js; do
|
for test in ./test/unit/*.js; do
|
||||||
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \
|
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 valgrind \
|
||||||
--suppressions=test/leak/libvips.supp \
|
--suppressions=test/leak/libvips.supp \
|
||||||
--suppressions=test/leak/sharp.supp \
|
--suppressions=test/leak/sharp.supp \
|
||||||
--gen-suppressions=yes \
|
--gen-suppressions=yes \
|
||||||
|
@ -225,14 +225,10 @@
|
|||||||
fun:FcInitLoadConfigAndFonts
|
fun:FcInitLoadConfigAndFonts
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
leak_fontconfig_doContent
|
leak_fontconfig_XML_ParseBuffer
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
match-leak-kinds: definite
|
match-leak-kinds: definite
|
||||||
fun:malloc
|
|
||||||
...
|
...
|
||||||
fun:doContent
|
|
||||||
fun:doProlog
|
|
||||||
fun:prologInitProcessor
|
|
||||||
fun:XML_ParseBuffer
|
fun:XML_ParseBuffer
|
||||||
obj:*/libfontconfig.so.*
|
obj:*/libfontconfig.so.*
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ describe('GIF input', () => {
|
|||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
assert.strictEqual(true, data.length > 0);
|
assert.strictEqual(true, data.length > 0);
|
||||||
assert.strictEqual(data.length, info.size);
|
assert.strictEqual(data.length, info.size);
|
||||||
assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
|
assert.strictEqual('gif', info.format);
|
||||||
assert.strictEqual(80, info.width);
|
assert.strictEqual(80, info.width);
|
||||||
assert.strictEqual(80, info.height);
|
assert.strictEqual(80, info.height);
|
||||||
assert.strictEqual(4, info.channels);
|
assert.strictEqual(4, info.channels);
|
||||||
@ -55,28 +55,30 @@ describe('GIF input', () => {
|
|||||||
.then(({ data, info }) => {
|
.then(({ data, info }) => {
|
||||||
assert.strictEqual(true, data.length > 0);
|
assert.strictEqual(true, data.length > 0);
|
||||||
assert.strictEqual(data.length, info.size);
|
assert.strictEqual(data.length, info.size);
|
||||||
assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
|
assert.strictEqual('gif', info.format);
|
||||||
assert.strictEqual(80, info.width);
|
assert.strictEqual(80, info.width);
|
||||||
assert.strictEqual(2400, info.height);
|
assert.strictEqual(2400, info.height);
|
||||||
assert.strictEqual(4, info.channels);
|
assert.strictEqual(4, info.channels);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!sharp.format.magick.output.buffer) {
|
it('GIF with reduced colours, no dither, low effort reduces file size', async () => {
|
||||||
it('GIF buffer output should fail due to missing ImageMagick', () => {
|
const original = await sharp(fixtures.inputJpg)
|
||||||
assert.throws(
|
.resize(120, 80)
|
||||||
() => sharp().gif(),
|
.gif()
|
||||||
/GIF output requires libvips with support for ImageMagick/
|
.toBuffer();
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('GIF file output should fail due to missing ImageMagick', () => {
|
const reduced = await sharp(fixtures.inputJpg)
|
||||||
assert.rejects(
|
.resize(120, 80)
|
||||||
async () => await sharp().toFile('test.gif'),
|
.gif({
|
||||||
/GIF output requires libvips with support for ImageMagick/
|
colours: 128,
|
||||||
);
|
dither: 0,
|
||||||
|
effort: 1
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.strictEqual(true, reduced.length < original.length);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
it('invalid pageHeight throws', () => {
|
it('invalid pageHeight throws', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
@ -88,7 +90,6 @@ describe('GIF input', () => {
|
|||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().gif({ loop: -1 });
|
sharp().gif({ loop: -1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().gif({ loop: 65536 });
|
sharp().gif({ loop: 65536 });
|
||||||
});
|
});
|
||||||
@ -98,14 +99,39 @@ describe('GIF input', () => {
|
|||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().gif({ delay: [-1] });
|
sharp().gif({ delay: [-1] });
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().gif({ delay: [65536] });
|
sharp().gif({ delay: [65536] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('invalid colour throws', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ colours: 1 });
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ colours: 'fail' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid effort throws', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ effort: 0 });
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ effort: 'fail' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid dither throws', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ dither: 1.1 });
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().gif({ effort: 'fail' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should work with streams when only animated is set', function (done) {
|
it('should work with streams when only animated is set', function (done) {
|
||||||
if (sharp.format.magick.output.buffer) {
|
|
||||||
fs.createReadStream(fixtures.inputGifAnimated)
|
fs.createReadStream(fixtures.inputGifAnimated)
|
||||||
.pipe(sharp({ animated: true }))
|
.pipe(sharp({ animated: true }))
|
||||||
.gif()
|
.gif()
|
||||||
@ -115,13 +141,9 @@ describe('GIF input', () => {
|
|||||||
assert.strictEqual('gif', info.format);
|
assert.strictEqual('gif', info.format);
|
||||||
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with streams when only pages is set', function (done) {
|
it('should work with streams when only pages is set', function (done) {
|
||||||
if (sharp.format.magick.output.buffer) {
|
|
||||||
fs.createReadStream(fixtures.inputGifAnimated)
|
fs.createReadStream(fixtures.inputGifAnimated)
|
||||||
.pipe(sharp({ pages: -1 }))
|
.pipe(sharp({ pages: -1 }))
|
||||||
.gif()
|
.gif()
|
||||||
@ -131,8 +153,5 @@ describe('GIF input', () => {
|
|||||||
assert.strictEqual('gif', info.format);
|
assert.strictEqual('gif', info.format);
|
||||||
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -561,19 +561,6 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Autoconvert GIF input to PNG output', function (done) {
|
|
||||||
sharp(fixtures.inputGif)
|
|
||||||
.resize(320, 80)
|
|
||||||
.toFile(outputZoinks, function (err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, info.size > 0);
|
|
||||||
assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
rimraf(outputZoinks, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Force JPEG format for PNG input', function (done) {
|
it('Force JPEG format for PNG input', function (done) {
|
||||||
sharp(fixtures.inputPng)
|
sharp(fixtures.inputPng)
|
||||||
.resize(320, 80)
|
.resize(320, 80)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user