diff --git a/docs/api-operation.md b/docs/api-operation.md index 768697f91..c4a69cdce 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -18,6 +18,8 @@ The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if Only one rotation can occur per pipeline. Previous calls to `rotate` in the same pipeline will be ignored. +Multi-page images can only be rotated by 180 degrees. + Method order is important when rotating, resizing and/or extracting regions, for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`. diff --git a/docs/changelog.md b/docs/changelog.md index 0e143bb63..a418d0803 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,6 +13,9 @@ Requires libvips v8.15.0 * Ensure `extend` operation stays sequential when copying (regression in 0.32.0). [#3928](https://github.com/lovell/sharp/issues/3928) +* Improve error handling for unsupported multi-page rotation. + [#3940](https://github.com/lovell/sharp/issues/3940) + ### v0.33.1 - 17th December 2023 * Add support for Yarn Plug'n'Play filesystem layout. diff --git a/lib/operation.js b/lib/operation.js index 199a0ebec..ed6df8345 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -24,6 +24,8 @@ const is = require('./is'); * Only one rotation can occur per pipeline. * Previous calls to `rotate` in the same pipeline will be ignored. * + * Multi-page images can only be rotated by 180 degrees. + * * Method order is important when rotating, resizing and/or extracting regions, * for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`. * diff --git a/src/pipeline.cc b/src/pipeline.cc index 3e76cf8bf..791f208bc 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -96,6 +96,9 @@ class PipelineWorker : public Napi::AsyncWorker { baton->rotationAngle != 0.0); if (autoRotation != VIPS_ANGLE_D0) { + if (autoRotation != VIPS_ANGLE_D180) { + MultiPageUnsupported(nPages, "Rotate"); + } image = image.rot(autoRotation); autoRotation = VIPS_ANGLE_D0; } @@ -114,6 +117,9 @@ class PipelineWorker : public Napi::AsyncWorker { baton->flop = FALSE; } if (rotation != VIPS_ANGLE_D0) { + if (rotation != VIPS_ANGLE_D180) { + MultiPageUnsupported(nPages, "Rotate"); + } image = image.rot(rotation); rotation = VIPS_ANGLE_D0; } @@ -397,6 +403,9 @@ class PipelineWorker : public Napi::AsyncWorker { rotation != VIPS_ANGLE_D0); // Auto-rotate post-extract if (autoRotation != VIPS_ANGLE_D0) { + if (autoRotation != VIPS_ANGLE_D180) { + MultiPageUnsupported(nPages, "Rotate"); + } image = image.rot(autoRotation); } // Mirror vertically (up-down) about the x-axis @@ -409,6 +418,9 @@ class PipelineWorker : public Napi::AsyncWorker { } // Rotate post-extract 90-angle if (rotation != VIPS_ANGLE_D0) { + if (rotation != VIPS_ANGLE_D180) { + MultiPageUnsupported(nPages, "Rotate"); + } image = image.rot(rotation); } diff --git a/test/unit/rotate.js b/test/unit/rotate.js index aee20eade..e9e819afe 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -353,6 +353,21 @@ describe('Rotation', function () { ) ); + it('Animated image rotate 180', () => + assert.doesNotReject(() => sharp(fixtures.inputGifAnimated, { animated: true }) + .rotate(180) + .toBuffer() + ) + ); + + it('Animated image rotate non-180 rejects', () => + assert.rejects(() => sharp(fixtures.inputGifAnimated, { animated: true }) + .rotate(90) + .toBuffer(), + /Rotate is not supported for multi-page images/ + ) + ); + it('Multiple rotate emits warning', () => { let warningMessage = ''; const s = sharp();