Skip to content

Commit 701143a

Browse files
committed
Ensure animated GIF to WebP conversion retains loop #3394
1 parent 38b6f44 commit 701143a

File tree

5 files changed

+26
-13
lines changed

5 files changed

+26
-13
lines changed

docs/src/content/docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Requires libvips v8.16.1
88

99
### v0.34.2 - TBD
1010

11+
* Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0).
12+
[#3394](https://github.com/lovell/sharp/issues/3394)
13+
1114
* Ensure `pdfBackground` constructor property is used.
1215
[#4207](https://github.com/lovell/sharp/pull/4207)
1316

lib/constructor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ const Sharp = function (input, options) {
298298
withExif: {},
299299
withExifMerge: true,
300300
resolveWithObject: false,
301-
loop: 1,
301+
loop: -1,
302302
delay: [],
303303
// output format
304304
jpegQuality: 80,

src/common.cc

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -651,22 +651,21 @@ namespace sharp {
651651
*/
652652
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
653653
bool hasDelay = !delay.empty();
654-
655-
// Avoid a copy if none of the animation properties are needed.
656-
if (nPages == 1 && !hasDelay && loop == -1) return image;
657-
658-
if (delay.size() == 1) {
659-
// We have just one delay, repeat that value for all frames.
660-
delay.insert(delay.end(), nPages - 1, delay[0]);
661-
}
662-
663-
// Attaching metadata, need to copy the image.
664654
VImage copy = image.copy();
665655

666656
// Only set page-height if we have more than one page, or this could
667657
// accidentally turn into an animated image later.
668658
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
669-
if (hasDelay) copy.set("delay", delay);
659+
if (hasDelay) {
660+
if (delay.size() == 1) {
661+
// We have just one delay, repeat that value for all frames.
662+
delay.insert(delay.end(), nPages - 1, delay[0]);
663+
}
664+
copy.set("delay", delay);
665+
}
666+
if (nPages == 1 && !hasDelay && loop == -1) {
667+
loop = 1;
668+
}
670669
if (loop != -1) copy.set("loop", loop);
671670

672671
return copy;

src/pipeline.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ struct PipelineBaton {
384384
ensureAlpha(-1.0),
385385
colourspacePipeline(VIPS_INTERPRETATION_LAST),
386386
colourspace(VIPS_INTERPRETATION_LAST),
387-
loop(1),
387+
loop(-1),
388388
tileSize(256),
389389
tileOverlap(0),
390390
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

test/unit/gif.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,15 @@ describe('GIF input', () => {
238238
assert.strictEqual(1, loop);
239239
}
240240
});
241+
242+
it('Animated GIF to animated WebP merges identical frames', async () => {
243+
const webp = await sharp(fixtures.inputGifAnimated, { animated: true })
244+
.webp()
245+
.toBuffer();
246+
247+
const { delay, loop, pages } = await sharp(webp).metadata();
248+
assert.deepStrictEqual([120, 120, 90, 120, 120, 90, 120, 90, 30], delay);
249+
assert.strictEqual(0, loop);
250+
assert.strictEqual(9, pages);
251+
});
241252
});

0 commit comments

Comments
 (0)