diff --git a/docs/src/content/docs/changelog.md b/docs/src/content/docs/changelog.md index 81a4ec1d..a4eb97db 100644 --- a/docs/src/content/docs/changelog.md +++ b/docs/src/content/docs/changelog.md @@ -8,6 +8,9 @@ Requires libvips v8.16.1 ### v0.34.2 - TBD +* Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0). + [#3394](https://github.com/lovell/sharp/issues/3394) + * Ensure `pdfBackground` constructor property is used. [#4207](https://github.com/lovell/sharp/pull/4207) diff --git a/lib/constructor.js b/lib/constructor.js index 3b88fbd9..d9490068 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -298,7 +298,7 @@ const Sharp = function (input, options) { withExif: {}, withExifMerge: true, resolveWithObject: false, - loop: 1, + loop: -1, delay: [], // output format jpegQuality: 80, diff --git a/src/common.cc b/src/common.cc index fb85eefa..0f68be34 100644 --- a/src/common.cc +++ b/src/common.cc @@ -651,22 +651,21 @@ namespace sharp { */ VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector delay, int loop) { bool hasDelay = !delay.empty(); - - // Avoid a copy if none of the animation properties are needed. - if (nPages == 1 && !hasDelay && loop == -1) return image; - - if (delay.size() == 1) { - // We have just one delay, repeat that value for all frames. - delay.insert(delay.end(), nPages - 1, delay[0]); - } - - // Attaching metadata, need to copy the image. VImage copy = image.copy(); // Only set page-height if we have more than one page, or this could // accidentally turn into an animated image later. if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight); - if (hasDelay) copy.set("delay", delay); + if (hasDelay) { + if (delay.size() == 1) { + // We have just one delay, repeat that value for all frames. + delay.insert(delay.end(), nPages - 1, delay[0]); + } + copy.set("delay", delay); + } + if (nPages == 1 && !hasDelay && loop == -1) { + loop = 1; + } if (loop != -1) copy.set("loop", loop); return copy; diff --git a/src/pipeline.h b/src/pipeline.h index 4bb053fd..66a45f43 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -384,7 +384,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), diff --git a/test/unit/gif.js b/test/unit/gif.js index 3b041b69..93e4d46b 100644 --- a/test/unit/gif.js +++ b/test/unit/gif.js @@ -238,4 +238,15 @@ describe('GIF input', () => { assert.strictEqual(1, loop); } }); + + it('Animated GIF to animated WebP merges identical frames', async () => { + const webp = await sharp(fixtures.inputGifAnimated, { animated: true }) + .webp() + .toBuffer(); + + const { delay, loop, pages } = await sharp(webp).metadata(); + assert.deepStrictEqual([120, 120, 90, 120, 120, 90, 120, 90, 30], delay); + assert.strictEqual(0, loop); + assert.strictEqual(9, pages); + }); });