Upgrade to libvips v8.14.0-rc1

- Replace GIF 'optimise' option with 'reuse'
- Add 'progressive' option to GIF
- Add 'wrap' option to text creation
- Add 'formatMagick' property to *magick input metadata
This commit is contained in:
Lovell Fuller 2022-12-29 15:53:43 +00:00
parent 844deaf480
commit eac6e8b261
16 changed files with 151 additions and 38 deletions

View File

@ -63,6 +63,7 @@ Implements the [stream.Duplex][1] class.
* `options.text.dpi` **[number][14]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`) * `options.text.dpi` **[number][14]** the resolution (size) at which to render the text. Does not take effect if `height` is specified. (optional, default `72`)
* `options.text.rgba` **[boolean][15]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. (optional, default `false`) * `options.text.rgba` **[boolean][15]** set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. (optional, default `false`)
* `options.text.spacing` **[number][14]** text line height in points. Will use the font line height if none is specified. (optional, default `0`) * `options.text.spacing` **[number][14]** text line height in points. Will use the font line height if none is specified. (optional, default `0`)
* `options.text.wrap` **[number][14]** word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'. (optional, default `'word'`)
### Examples ### Examples

View File

@ -328,8 +328,8 @@ The palette of the input image will be re-used if possible.
* `options` **[Object][6]?** output options * `options` **[Object][6]?** output options
* `options.reoptimise` **[boolean][10]** always generate new palettes (slow), re-use existing by default (optional, default `false`) * `options.reuse` **[boolean][10]** re-use existing palette, otherwise generate new (slow) (optional, default `true`)
* `options.reoptimize` **[boolean][10]** alternative spelling of `options.reoptimise` (optional, default `false`) * `options.progressive` **[boolean][10]** use progressive (interlace) scan (optional, default `false`)
* `options.colours` **[number][12]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`) * `options.colours` **[number][12]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`)
* `options.colors` **[number][12]** alternative spelling of `options.colours` (optional, default `256`) * `options.colors` **[number][12]** alternative spelling of `options.colours` (optional, default `256`)
* `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`) * `options.effort` **[number][12]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`)

View File

@ -1,5 +1,28 @@
# Changelog # Changelog
## v0.32 - *flow*
Requires libvips v8.14.0
### v0.32.0 - TBD
* Replace GIF output `optimise` / `optimize` option with `reuse`.
* Add `progressive` option to GIF output for interlacing.
* Add `wrap` option to text image creation.
* Add `formatMagick` property to metadata of images loaded via *magick.
* Allow use of GPS (IFD3) EXIF metadata.
[#2767](https://github.com/lovell/sharp/issues/2767)
* Prebuilt binaries: ensure macOS 10.13+ support, as documented.
[#3438](https://github.com/lovell/sharp/issues/3438)
* Prebuilt binaries: prevent use of glib slice allocator, improves QEMU support.
[#3448](https://github.com/lovell/sharp/issues/3448)
## v0.31 - *eagle* ## v0.31 - *eagle*
Requires libvips v8.13.3 Requires libvips v8.13.3

View File

@ -158,6 +158,7 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified. * @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. * @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified. * @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {number} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@ -291,7 +292,8 @@ const Sharp = function (input, options) {
gifDither: 1, gifDither: 1,
gifInterFrameMaxError: 0, gifInterFrameMaxError: 0,
gifInterPaletteMaxError: 3, gifInterPaletteMaxError: 3,
gifReoptimise: false, gifReuse: true,
gifProgressive: false,
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',

View File

@ -327,6 +327,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing); throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
} }
} }
if (is.defined(inputOptions.text.wrap)) {
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
inputDescriptor.textWrap = inputOptions.text.wrap;
} else {
throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
}
}
delete inputDescriptor.buffer; delete inputDescriptor.buffer;
} else { } else {
throw new Error('Expected a valid string to create an image with text.'); throw new Error('Expected a valid string to create an image with text.');

View File

@ -559,8 +559,8 @@ function webp (options) {
* .toFile('optim.gif'); * .toFile('optim.gif');
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {boolean} [options.reoptimise=false] - always generate new palettes (slow), re-use existing by default * @param {boolean} [options.reuse=true] - re-use existing palette, otherwise generate new (slow)
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise` * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256 * @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.colors=256] - alternative spelling of `options.colours`
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest) * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
@ -575,10 +575,11 @@ function webp (options) {
*/ */
function gif (options) { function gif (options) {
if (is.object(options)) { if (is.object(options)) {
if (is.defined(options.reoptimise)) { if (is.defined(options.reuse)) {
this._setBooleanOption('gifReoptimise', options.reoptimise); this._setBooleanOption('gifReuse', options.reuse);
} else if (is.defined(options.reoptimize)) { }
this._setBooleanOption('gifReoptimise', options.reoptimize); if (is.defined(options.progressive)) {
this._setBooleanOption('gifProgressive', options.progressive);
} }
const colours = options.colours || options.colors; const colours = options.colours || options.colors;
if (is.defined(colours)) { if (is.defined(colours)) {
@ -690,7 +691,7 @@ function jp2 (options) {
} }
if (is.defined(options.chromaSubsampling)) { if (is.defined(options.chromaSubsampling)) {
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) { if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
this.options.heifChromaSubsampling = options.chromaSubsampling; this.options.jp2ChromaSubsampling = options.chromaSubsampling;
} else { } else {
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling); throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
} }

View File

@ -155,19 +155,19 @@
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.13.3", "libvips": "8.14.0-rc1",
"integrity": { "integrity": {
"darwin-arm64v8": "sha512-xFgYt7CtQSZcWoyUdzPTDNHbioZIrZSEU+gkMxzH4Cgjhi4/N49UsonnIZhKQoTBGloAqEexHeMx4rYTQ2Kgvw==", "darwin-arm64v8": "sha512-WWVRbtTdBGx7l0mZ1RkMsNzVkiR3923knivO21RKyRm2q6PJZunAd9Da3wVBfGnofhirlsbRkQLwPbFsdq/lkA==",
"darwin-x64": "sha512-6SivWKzu15aUMMohe0wg7sNYMPETVnOe40BuWsnKOgzl3o5FpQqNSgs+68Mi8Za3Qti9/DaR+H/fyD0x48Af2w==", "darwin-x64": "sha512-tyYkU/Mma1LKbg2PQELpoeECw/SOp5BqKwasIuEAlddC7g1JWLpIYrxuAbH1kaWy3soEFBJbN1CxRNL0YWy9Ug==",
"linux-arm64v8": "sha512-b+iI9V/ehgDabXYRQcvqa5CEysh+1FQsgFmYc358StCrJCDahwNmsQdsiH1GOVd5WaWh5wHUGByPwMmFOO16Aw==", "linux-arm64v8": "sha512-m7yr3ZSL3NlEpDOlUrScTYR4Rj1b5cSt8PmWvIkOtEGRJjtvFufurCDroOaaOIdKi1uB5n23ziB2PwTZX7tyAA==",
"linux-armv6": "sha512-zRP2F+EiustLE4bXSH8AHCxwfemh9d+QuvmPjira/HL6uJOUuA7SyQgVV1TPwTQle2ioCNnKPm7FEB/MAiT+ug==", "linux-armv6": "sha512-dy2t/7uyqjKXCgiYDbrouFp3pNqpzsPNpgkk3uT/c3wAhNcRvpVHbYMMrau0o/o9YULLLQ5aBW9HgEvjHCaqZw==",
"linux-armv7": "sha512-6OCChowE5lBXXXAZrnGdA9dVktg7UdODEBpE5qTroiAJYZv4yXRMgyDFYajok7du2NTgoklhxGk8d9+4vGv5hg==", "linux-armv7": "sha512-5bJhPzDrX3IaWU9Z3qMtE/C20bFAynN9YhaPF8gqFBMRkyif1ZnAlZl6PlVg/c968ABB4vJcAQJf4ImZJvBZLQ==",
"linux-x64": "sha512-OTmlmP2r8ozGKdB96X+K5oQE1ojVZanqLqqKlwDpEnfixyIaDGYbVzcjWBNGU3ai/26bvkaCkjynnc2ecYcsuA==", "linux-x64": "sha512-13nlg8oc0V/uxFa7r+QIJpIOVZstxM2CE1LSUI7SUmy4h6h+Xf0ztMi+6w+kL7LibtLWpjTEHQuCBNxSWdt2VA==",
"linuxmusl-arm64v8": "sha512-Qh5Wi+bkKTohFYHzSPssfjMhIkD6z6EHbVmnwmWSsgY9zsUBStFp6+mKcNTQfP5YM5Mz06vJOkLHX2OzEr5TzA==", "linuxmusl-arm64v8": "sha512-3RrraBIynOBDQuKwhiDrulRXZh1BfiDfy2SIyEgkPC301UdGw4FHQntgYc9E6K9CzYGv8jb1S9/8hcrn2/+LyA==",
"linuxmusl-x64": "sha512-DwB4Fs3+ISw9etaLCANkueZDdk758iOS+wNp4TKZkHdq0al6B/3Pk7OHLR8a9E3H6wYDD328u++dcJzip5tacA==", "linuxmusl-x64": "sha512-K+5eujn+pgH/vDcAJrtbTfZxJn9W9pfvJlTrtF0AjZdOBMwt9fcCeCeNVvKzKYcemzFmBHsjqxkgqlxA4xEnXQ==",
"win32-arm64v8": "sha512-96r3W+O4BtX602B1MtxU5Ru4lKzRRTZqM4OQEBJ//TNL3fiCZdd9agD+RQBjaeR4KFIyBSt3F7IE425ZWmxz+w==", "win32-arm64v8": "sha512-VliwICXWqGERjk2I3wsw6qwFVtHZZ9jljNW8Xev51M9Y/gdttzVk+fWqiikhJD5K2Gp5sg1ccbvEsu42ZVQXxA==",
"win32-ia32": "sha512-qfN1MsfQGek1QQd1UNW7JT+5K5Ne1suFQ2GpgpYm3JLSpIve/tz2vOGEGzvTVssOBADJvAkTDFt+yIi3PgU9pA==", "win32-ia32": "sha512-dNJoDRDkYzkNCz0gGtIahOf8n2g+dBg0iNVHvYDAJV7qLfjAqtfQJZ9TG2p4PzPCM2H4kazSNL/JOxFF4bpc1g==",
"win32-x64": "sha512-eb3aAmjbVVBVRbiYgebQwoxkAt69WI8nwmKlilSQ3kWqoc0pXfIe322rF2UR8ebbISCGvYRUfzD2r1k92RXISQ==" "win32-x64": "sha512-myvHzlVgjztJ//crm8daXyGUa7ZciRfj3+L/faqyZsX2FMr4SImw4OO2UkCvDLJz6Fxar4biau3kAeGDJLCIQw=="
}, },
"runtime": "napi", "runtime": "napi",
"target": 7 "target": 7

View File

@ -167,6 +167,9 @@ namespace sharp {
if (HasAttr(input, "textSpacing")) { if (HasAttr(input, "textSpacing")) {
descriptor->textSpacing = AttrAsUint32(input, "textSpacing"); descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
} }
if (HasAttr(input, "textWrap")) {
descriptor->textWrap = AttrAsEnum<VipsTextWrap>(input, "textWrap", VIPS_TYPE_TEXT_WRAP);
}
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels")); descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
@ -454,6 +457,7 @@ namespace sharp {
->set("justify", descriptor->textJustify) ->set("justify", descriptor->textJustify)
->set("rgba", descriptor->textRgba) ->set("rgba", descriptor->textRgba)
->set("spacing", descriptor->textSpacing) ->set("spacing", descriptor->textSpacing)
->set("wrap", descriptor->textWrap)
->set("autofit_dpi", &descriptor->textAutofitDpi); ->set("autofit_dpi", &descriptor->textAutofitDpi);
if (descriptor->textWidth > 0) { if (descriptor->textWidth > 0) {
textOptions->set("width", descriptor->textWidth); textOptions->set("width", descriptor->textWidth);
@ -612,6 +616,15 @@ namespace sharp {
return copy; return copy;
} }
/*
Remove GIF palette from image.
*/
VImage RemoveGifPalette(VImage image) {
VImage copy = image.copy();
copy.remove("gif-palette");
return copy;
}
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */

View File

@ -25,9 +25,9 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8) || \ #if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 13) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 14) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 3) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 14 && VIPS_MICRO_VERSION < 0)
#error "libvips version 8.13.3+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.14.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@ -81,6 +81,7 @@ namespace sharp {
int textDpi; int textDpi;
bool textRgba; bool textRgba;
int textSpacing; int textSpacing;
VipsTextWrap textWrap;
int textAutofitDpi; int textAutofitDpi;
InputDescriptor(): InputDescriptor():
@ -114,6 +115,7 @@ namespace sharp {
textDpi(72), textDpi(72),
textRgba(FALSE), textRgba(FALSE),
textSpacing(0), textSpacing(0),
textWrap(VIPS_TEXT_WRAP_WORD),
textAutofitDpi(0) {} textAutofitDpi(0) {}
}; };
@ -255,6 +257,11 @@ namespace sharp {
*/ */
VImage RemoveAnimationProperties(VImage image); VImage RemoveAnimationProperties(VImage image);
/*
Remove GIF palette from image.
*/
VImage RemoveGifPalette(VImage image);
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */

View File

@ -3679,6 +3679,13 @@ VipsBlob *VImage::webpsave_buffer( VOption *options ) const
return( buffer ); return( buffer );
} }
void VImage::webpsave_mime( VOption *options ) const
{
call( "webpsave_mime",
(options ? options : VImage::option())->
set( "in", *this ) );
}
void VImage::webpsave_target( VTarget target, VOption *options ) const void VImage::webpsave_target( VTarget target, VOption *options ) const
{ {
call( "webpsave_target", call( "webpsave_target",

View File

@ -80,6 +80,9 @@ class MetadataWorker : public Napi::AsyncWorker {
if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) { if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) {
baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT); baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT);
} }
if (image.get_typeof("magick-format") == VIPS_TYPE_REF_STRING) {
baton->formatMagick = image.get_string("magick-format");
}
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) { if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
int const levels = std::stoi(image.get_string("openslide.level-count")); int const levels = std::stoi(image.get_string("openslide.level-count"));
for (int l = 0; l < levels; l++) { for (int l = 0; l < levels; l++) {
@ -204,6 +207,9 @@ class MetadataWorker : public Napi::AsyncWorker {
if (!baton->resolutionUnit.empty()) { if (!baton->resolutionUnit.empty()) {
info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit); info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit);
} }
if (!baton->formatMagick.empty()) {
info.Set("formatMagick", baton->formatMagick);
}
if (!baton->levels.empty()) { if (!baton->levels.empty()) {
int i = 0; int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size())); Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));

View File

@ -41,6 +41,7 @@ struct MetadataBaton {
int pagePrimary; int pagePrimary;
std::string compression; std::string compression;
std::string resolutionUnit; std::string resolutionUnit;
std::string formatMagick;
std::vector<std::pair<int, int>> levels; std::vector<std::pair<int, int>> levels;
int subifds; int subifds;
std::vector<double> background; std::vector<double> background;

View File

@ -409,6 +409,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.bandjoin(joinImage); image = image.bandjoin(joinImage);
} }
image = image.copy(VImage::option()->set("interpretation", baton->colourspace)); image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
image = sharp::RemoveGifPalette(image);
} }
inputWidth = image.width(); inputWidth = image.width();
@ -660,6 +661,7 @@ class PipelineWorker : public Napi::AsyncWorker {
ys.push_back(top); ys.push_back(top);
} }
image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys)); image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
image = sharp::RemoveGifPalette(image);
} }
// Gamma decoding (brighten) // Gamma decoding (brighten)
@ -689,6 +691,7 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput); booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::Boolean(image, booleanImage, baton->booleanOp);
image = sharp::RemoveGifPalette(image);
} }
// Apply per-channel Bandbool bitwise operations after all other operations // Apply per-channel Bandbool bitwise operations after all other operations
@ -870,7 +873,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("bitdepth", baton->gifBitdepth) ->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reoptimise", baton->gifReoptimise) ->set("reuse", baton->gifReuse)
->set("interlace", baton->gifProgressive)
->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interframe_maxerror", baton->gifInterFrameMaxError)
->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
->set("dither", baton->gifDither))); ->set("dither", baton->gifDither)));
@ -1068,7 +1072,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("bitdepth", baton->gifBitdepth) ->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reoptimise", baton->gifReoptimise) ->set("reuse", baton->gifReuse)
->set("interlace", baton->gifProgressive)
->set("dither", baton->gifDither)); ->set("dither", baton->gifDither));
baton->formatOut = "gif"; baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@ -1582,7 +1587,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
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");

View File

@ -165,7 +165,8 @@ struct PipelineBaton {
double gifDither; double gifDither;
double gifInterFrameMaxError; double gifInterFrameMaxError;
double gifInterPaletteMaxError; double gifInterPaletteMaxError;
bool gifReoptimise; bool gifReuse;
bool gifProgressive;
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
@ -322,7 +323,8 @@ struct PipelineBaton {
gifDither(1.0), gifDither(1.0),
gifInterFrameMaxError(0.0), gifInterFrameMaxError(0.0),
gifInterPaletteMaxError(3.0), gifInterPaletteMaxError(3.0),
gifReoptimise(false), gifReuse(true),
gifProgressive(false),
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),

View File

@ -80,19 +80,36 @@ describe('GIF input', () => {
assert.strictEqual(true, reduced.length < original.length); assert.strictEqual(true, reduced.length < original.length);
}); });
it('valid optimise', () => { it('valid reuse', () => {
assert.doesNotThrow(() => sharp().gif({ reoptimise: true })); assert.doesNotThrow(() => sharp().gif({ reuse: true }));
assert.doesNotThrow(() => sharp().gif({ reoptimize: true })); assert.doesNotThrow(() => sharp().gif({ reuse: false }));
}); });
it('invalid reoptimise throws', () => { it('invalid reuse throws', () => {
assert.throws( assert.throws(
() => sharp().gif({ reoptimise: -1 }), () => sharp().gif({ reuse: -1 }),
/Expected boolean for gifReoptimise but received -1 of type number/ /Expected boolean for gifReuse but received -1 of type number/
); );
assert.throws( assert.throws(
() => sharp().gif({ reoptimize: 'fail' }), () => sharp().gif({ reuse: 'fail' }),
/Expected boolean for gifReoptimise but received fail of type string/ /Expected boolean for gifReuse but received fail of type string/
);
});
it('progressive changes file size', async () => {
const nonProgressive = await sharp(fixtures.inputGif).gif({ progressive: false }).toBuffer();
const progressive = await sharp(fixtures.inputGif).gif({ progressive: true }).toBuffer();
assert(nonProgressive.length !== progressive.length);
});
it('invalid progressive throws', () => {
assert.throws(
() => sharp().gif({ progressive: -1 }),
/Expected boolean for gifProgressive but received -1 of type number/
);
assert.throws(
() => sharp().gif({ progressive: 'fail' }),
/Expected boolean for gifProgressive but received fail of type string/
); );
}); });

View File

@ -294,4 +294,24 @@ describe('Text to image', () => {
}); });
}); });
}); });
it('valid wrap throws', () => {
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'none' } }));
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'wordChar' } }));
});
it('invalid wrap throws', () => {
assert.throws(
() => sharp({ text: { text: 'text', wrap: 1 } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received 1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', wrap: false } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received false of type boolean/
);
assert.throws(
() => sharp({ text: { text: 'text', wrap: 'invalid' } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received invalid of type string/
);
});
}); });