diff --git a/docs/changelog.md b/docs/changelog.md index c54b2a17..d2e1d042 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -43,6 +43,9 @@ Requires libvips v8.12.1 [#3006](https://github.com/lovell/sharp/pull/3006) [@christopherbradleybanks](https://github.com/christopherbradleybanks) +* Ensure rotate-then-extract works with EXIF mirroring. + [#3024](https://github.com/lovell/sharp/issues/3024) + ## v0.29 - *circle* Requires libvips v8.11.3 diff --git a/src/pipeline.cc b/src/pipeline.cc index b88d8944..23988535 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -80,13 +80,11 @@ class PipelineWorker : public Napi::AsyncWorker { // Calculate angle of rotation VipsAngle rotation; + bool flip = FALSE; + bool flop = FALSE; if (baton->useExifOrientation) { // Rotate and flip image according to Exif orientation - bool flip; - bool flop; std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image)); - baton->flip = baton->flip || flip; - baton->flop = baton->flop || flop; } else { rotation = CalculateAngleRotation(baton->angle); } @@ -95,6 +93,14 @@ class PipelineWorker : public Napi::AsyncWorker { if (baton->rotateBeforePreExtract) { if (rotation != VIPS_ANGLE_D0) { image = image.rot(rotation); + if (flip) { + image = image.flip(VIPS_DIRECTION_VERTICAL); + flip = FALSE; + } + if (flop) { + image = image.flip(VIPS_DIRECTION_HORIZONTAL); + flop = FALSE; + } image = sharp::RemoveExifOrientation(image); } if (baton->rotationAngle != 0.0) { @@ -384,20 +390,27 @@ class PipelineWorker : public Napi::AsyncWorker { } // Rotate post-extract 90-angle - if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) { - image = image.rot(rotation); - image = sharp::RemoveExifOrientation(image); + if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) { + image = image.rot(rotation); + if (flip) { + image = image.flip(VIPS_DIRECTION_VERTICAL); + flip = FALSE; + } + if (flop) { + image = image.flip(VIPS_DIRECTION_HORIZONTAL); + flop = FALSE; + } + image = sharp::RemoveExifOrientation(image); } - // Flip (mirror about Y axis) - if (baton->flip) { + if (baton->flip || flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); image = sharp::RemoveExifOrientation(image); } // Flop (mirror about X axis) - if (baton->flop) { + if (baton->flop || flop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); image = sharp::RemoveExifOrientation(image); } diff --git a/test/fixtures/expected/rotate-mirror-extract.jpg b/test/fixtures/expected/rotate-mirror-extract.jpg new file mode 100644 index 00000000..46d5a93b Binary files /dev/null and b/test/fixtures/expected/rotate-mirror-extract.jpg differ diff --git a/test/unit/extract.js b/test/unit/extract.js index 442638dc..4120cb27 100644 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -168,6 +168,16 @@ describe('Partial image extraction', function () { }); }); + it('Rotate with EXIF mirroring then extract', function (done) { + sharp(fixtures.inputJpgWithLandscapeExif7) + .rotate() + .extract({ left: 0, top: 208, width: 60, height: 40 }) + .toBuffer(function (err, data) { + if (err) throw err; + fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done); + }); + }); + describe('Invalid parameters', function () { describe('using the legacy extract(top,left,width,height) syntax', function () { it('String top', function () {