From f50057a131997f2af981df3f79fcf69a3b78a970 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 8 Oct 2025 16:42:38 +0200 Subject: [PATCH 1/4] Fix failing auto rotation with sequential access --- src/Decoders/NativeObjectDecoder.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Decoders/NativeObjectDecoder.php b/src/Decoders/NativeObjectDecoder.php index 2b0b67c..0675921 100644 --- a/src/Decoders/NativeObjectDecoder.php +++ b/src/Decoders/NativeObjectDecoder.php @@ -35,15 +35,18 @@ public function decode(mixed $input): ImageInterface|ColorInterface throw new DecoderException('Unable to decode input'); } + $core = new Core($input); + // auto-rotate - if ($this->driver()->config()->autoOrientation === true) { - $input = $input->autorot(); + if ($this->driver()->config()->autoOrientation === true && $this->exifRotation($input) > 1) { + // autorot() does not seem to work with the default sequential access of this library + $core->setNative(Core::ensureInMemory($core)->native()->autorot()); } // build image instance $image = new Image( $this->driver(), - new Core($input) + $core ); // set media type on origin @@ -96,4 +99,22 @@ protected function vipsMediaType(VipsImage $vips): ?MediaType default => null }; } + + /** + * Return the exif rotation of the given image or null if there isn't any + */ + protected function exifRotation(VipsImage $vips): ?int + { + if (!in_array('exif-ifd0-Orientation', $vips->getFields())) { + return null; + } + + $orientation = substr($vips->get('exif-ifd0-Orientation'), 0, 1); + + if (!is_numeric($orientation)) { + return null; + } + + return (int) $orientation; + } } From c1d90ff3ee1dc53ce3e8c1a1b70b406946944e2e Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 8 Oct 2025 17:03:43 +0200 Subject: [PATCH 2/4] Refactor code --- src/Decoders/NativeObjectDecoder.php | 22 ++++++++++++---------- src/Modifiers/AlignRotationModifier.php | 4 +++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Decoders/NativeObjectDecoder.php b/src/Decoders/NativeObjectDecoder.php index 0675921..f3f4726 100644 --- a/src/Decoders/NativeObjectDecoder.php +++ b/src/Decoders/NativeObjectDecoder.php @@ -6,6 +6,7 @@ use Intervention\Image\Drivers\SpecializableDecoder; use Intervention\Image\Drivers\Vips\Core; +use Intervention\Image\Drivers\Vips\Modifiers\AlignRotationModifier; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Image; @@ -35,20 +36,17 @@ public function decode(mixed $input): ImageInterface|ColorInterface throw new DecoderException('Unable to decode input'); } - $core = new Core($input); - - // auto-rotate - if ($this->driver()->config()->autoOrientation === true && $this->exifRotation($input) > 1) { - // autorot() does not seem to work with the default sequential access of this library - $core->setNative(Core::ensureInMemory($core)->native()->autorot()); - } - // build image instance $image = new Image( $this->driver(), - $core + new Core($input) ); + // auto-rotate + if ($this->driver()->config()->autoOrientation === true && $this->exifRotation($input) > 1) { + $image->modify(new AlignRotationModifier()); + } + // set media type on origin if ($mediaType = $this->vipsMediaType($input)) { $image->origin()->setMediaType($mediaType); @@ -109,7 +107,11 @@ protected function exifRotation(VipsImage $vips): ?int return null; } - $orientation = substr($vips->get('exif-ifd0-Orientation'), 0, 1); + try { + $orientation = substr($vips->get('exif-ifd0-Orientation'), 0, 1); + } catch (VipsException) { + return null; + } if (!is_numeric($orientation)) { return null; diff --git a/src/Modifiers/AlignRotationModifier.php b/src/Modifiers/AlignRotationModifier.php index d0440d7..0ddb437 100644 --- a/src/Modifiers/AlignRotationModifier.php +++ b/src/Modifiers/AlignRotationModifier.php @@ -4,6 +4,7 @@ namespace Intervention\Image\Drivers\Vips\Modifiers; +use Intervention\Image\Drivers\Vips\Core; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SpecializedInterface; use Intervention\Image\Modifiers\AlignRotationModifier as GenericAlignRotationModifier; @@ -18,7 +19,8 @@ class AlignRotationModifier extends GenericAlignRotationModifier implements Spec public function apply(ImageInterface $image): ImageInterface { $image->core()->setNative( - $image->core()->native()->autorot() + // autorot() does not seem to work with the default sequential access of this library + Core::ensureInMemory($image->core())->native()->autorot() ); return $image; From 45bd8d6033c3b73eaab38126aa8da56e1b1fbd68 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Thu, 9 Oct 2025 16:22:45 +0200 Subject: [PATCH 3/4] Simplify code --- src/Decoders/NativeObjectDecoder.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Decoders/NativeObjectDecoder.php b/src/Decoders/NativeObjectDecoder.php index f3f4726..7229881 100644 --- a/src/Decoders/NativeObjectDecoder.php +++ b/src/Decoders/NativeObjectDecoder.php @@ -103,20 +103,16 @@ protected function vipsMediaType(VipsImage $vips): ?MediaType */ protected function exifRotation(VipsImage $vips): ?int { - if (!in_array('exif-ifd0-Orientation', $vips->getFields())) { + if (!in_array('orientation', $vips->getFields())) { return null; } try { - $orientation = substr($vips->get('exif-ifd0-Orientation'), 0, 1); + $orientation = $vips->get('orientation'); } catch (VipsException) { return null; } - if (!is_numeric($orientation)) { - return null; - } - - return (int) $orientation; + return is_int($orientation) ? $orientation : null; } } From e94f2061072cbacf1a75480becb03c2b38ad4329 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 15 Oct 2025 17:11:57 +0200 Subject: [PATCH 4/4] Add test --- .../Modifiers/AlignRotationModifierTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Unit/Modifiers/AlignRotationModifierTest.php b/tests/Unit/Modifiers/AlignRotationModifierTest.php index 1c9ecaa..f76c3f5 100644 --- a/tests/Unit/Modifiers/AlignRotationModifierTest.php +++ b/tests/Unit/Modifiers/AlignRotationModifierTest.php @@ -15,9 +15,29 @@ public function testApply(): void $image = (new ImageManager(Driver::class, autoOrientation: false))->read( $this->getTestResourcePath('orientation.jpg') ); + $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); $result = $image->orient(); $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); $this->assertColor(1, 0, 254, 255, $result->pickColor(3, 3)); } + + /** + * According to libvips/libvips#4475 you can't use autorot with + * orientated JPEGs and sequential access. This test checks the negative + * scenario, before the fix. + */ + public function testAutoOrientationSave(): void + { + $tmpFile = sys_get_temp_dir() . '/out.jpg'; + + (new ImageManager(Driver::class, autoOrientation: true))->read( + $this->getTestResourcePath('orientation.jpg') + )->save($tmpFile); + + $this->assertFileExists($tmpFile); + + // remove tmp file + unlink($tmpFile); + } }