Non-animated GIF output defaults to no-loop #3394

This commit is contained in:
Lovell Fuller 2025-03-21 09:36:25 +00:00
parent 3fd818c4b5
commit 3e41f8b65e
5 changed files with 22 additions and 8 deletions

View File

@ -14,6 +14,9 @@ Requires libvips v8.16.1
* Breaking: Ensure `removeAlpha` removes all alpha channels.
[#2266](https://github.com/lovell/sharp/issues/2266)
* Breaking: Non-animated GIF output defaults to no-loop instead of loop-forever.
[#3394](https://github.com/lovell/sharp/issues/3394)
* Breaking: Support `info.size` on wide-character systems via upgrade to C++17.
[#3943](https://github.com/lovell/sharp/issues/3943)

View File

@ -296,6 +296,8 @@ const Sharp = function (input, options) {
withExif: {},
withExifMerge: true,
resolveWithObject: false,
loop: 1,
delay: [],
// output format
jpegQuality: 80,
jpegProgressive: false,

View File

@ -1705,6 +1705,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
}
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
baton->loop = sharp::AttrAsUint32(options, "loop");
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
// Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@ -1774,13 +1776,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
// Animated output properties
if (sharp::HasAttr(options, "loop")) {
baton->loop = sharp::AttrAsUint32(options, "loop");
}
if (sharp::HasAttr(options, "delay")) {
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
}
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");

View File

@ -380,7 +380,7 @@ struct PipelineBaton {
ensureAlpha(-1.0),
colourspacePipeline(VIPS_INTERPRETATION_LAST),
colourspace(VIPS_INTERPRETATION_LAST),
loop(-1),
loop(1),
tileSize(256),
tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@ -224,4 +224,18 @@ describe('GIF input', () => {
const after = await input.gif({ interPaletteMaxError: 100 }).toBuffer();
assert.strict(before.length > after.length);
});
it('non-animated input defaults to no-loop', async () => {
for (const input of [fixtures.inputGif, fixtures.inputPng]) {
const data = await sharp(input)
.resize(8)
.gif({ effort: 1 })
.toBuffer();
const { format, pages, loop } = await sharp(data).metadata();
assert.strictEqual('gif', format);
assert.strictEqual(1, pages);
assert.strictEqual(1, loop);
}
});
});