Skip to content

Commit 3e41f8b

Browse files
committed
Non-animated GIF output defaults to no-loop #3394
1 parent 3fd818c commit 3e41f8b

5 files changed

Lines changed: 22 additions & 8 deletions

File tree

docs/src/content/docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Requires libvips v8.16.1
1414
* Breaking: Ensure `removeAlpha` removes all alpha channels.
1515
[#2266](https://github.com/lovell/sharp/issues/2266)
1616

17+
* Breaking: Non-animated GIF output defaults to no-loop instead of loop-forever.
18+
[#3394](https://github.com/lovell/sharp/issues/3394)
19+
1720
* Breaking: Support `info.size` on wide-character systems via upgrade to C++17.
1821
[#3943](https://github.com/lovell/sharp/issues/3943)
1922

lib/constructor.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ const Sharp = function (input, options) {
296296
withExif: {},
297297
withExifMerge: true,
298298
resolveWithObject: false,
299+
loop: 1,
300+
delay: [],
299301
// output format
300302
jpegQuality: 80,
301303
jpegProgressive: false,

src/pipeline.cc

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
17051705
}
17061706
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
17071707
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1708+
baton->loop = sharp::AttrAsUint32(options, "loop");
1709+
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
17081710
// Format-specific
17091711
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
17101712
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@@ -1774,13 +1776,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
17741776
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
17751777
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
17761778
baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
1777-
// Animated output properties
1778-
if (sharp::HasAttr(options, "loop")) {
1779-
baton->loop = sharp::AttrAsUint32(options, "loop");
1780-
}
1781-
if (sharp::HasAttr(options, "delay")) {
1782-
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
1783-
}
17841779
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
17851780
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
17861781
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");

src/pipeline.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ struct PipelineBaton {
380380
ensureAlpha(-1.0),
381381
colourspacePipeline(VIPS_INTERPRETATION_LAST),
382382
colourspace(VIPS_INTERPRETATION_LAST),
383-
loop(-1),
383+
loop(1),
384384
tileSize(256),
385385
tileOverlap(0),
386386
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

test/unit/gif.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,18 @@ describe('GIF input', () => {
224224
const after = await input.gif({ interPaletteMaxError: 100 }).toBuffer();
225225
assert.strict(before.length > after.length);
226226
});
227+
228+
it('non-animated input defaults to no-loop', async () => {
229+
for (const input of [fixtures.inputGif, fixtures.inputPng]) {
230+
const data = await sharp(input)
231+
.resize(8)
232+
.gif({ effort: 1 })
233+
.toBuffer();
234+
235+
const { format, pages, loop } = await sharp(data).metadata();
236+
assert.strictEqual('gif', format);
237+
assert.strictEqual(1, pages);
238+
assert.strictEqual(1, loop);
239+
}
240+
});
227241
});

0 commit comments

Comments
 (0)