From b8234a95c616a389754406cc28b73817d623b4f6 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 18 May 2026 09:51:18 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Swap=20image=20dimensions=20when=20EXIF=20o?= =?UTF-8?q?rientation=20indicates=20a=2090=C2=B0=20rotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Orientations 5–8 all involve a 90° rotation, so the stored pixel dimensions are transposed relative to how the image is actually displayed. Mirrors the existing behaviour for rotated videos. Co-Authored-By: Claude Sonnet 4.6 --- src/Imaging/Attributes.php | 9 ++++++ tests/Assets/AttributesTest.php | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/Imaging/Attributes.php b/src/Imaging/Attributes.php index 8b053f57e4e..983b0ecdbfe 100644 --- a/src/Imaging/Attributes.php +++ b/src/Imaging/Attributes.php @@ -55,6 +55,15 @@ private function imageAttributes(string $path) [$width, $height] = $size; + if (function_exists('exif_read_data')) { + $exif = @exif_read_data($fullPath); + $orientation = $exif['Orientation'] ?? 1; + + if (in_array($orientation, [5, 6, 7, 8])) { + [$width, $height] = [$height, $width]; + } + } + return compact('width', 'height'); } diff --git a/tests/Assets/AttributesTest.php b/tests/Assets/AttributesTest.php index 87fc3d02cab..f981c1f00de 100644 --- a/tests/Assets/AttributesTest.php +++ b/tests/Assets/AttributesTest.php @@ -152,6 +152,61 @@ public function it_uses_default_attributes_if_the_svg_has_no_viewbox_and_is_miss $this->assertEquals(['width' => 300, 'height' => 150], $this->attributes->asset($this->svgAsset(''))->get()); } + #[Test] + #[DataProvider('exifOrientationProvider')] + public function it_respects_exif_orientation_when_getting_image_dimensions($orientation, $expectedWidth, $expectedHeight) + { + $asset = (new Asset) + ->container(AssetContainer::make('test-container')->disk('test')) + ->path('path/to/asset.jpg'); + + $jpeg = $this->createJpegWithOrientation(4032, 3024, $orientation); + Storage::disk('test')->put('path/to/asset.jpg', $jpeg); + + $attributes = $this->attributes->asset($asset)->get(); + + $this->assertEquals($expectedWidth, $attributes['width']); + $this->assertEquals($expectedHeight, $attributes['height']); + } + + public static function exifOrientationProvider() + { + return [ + 'orientation 1 (normal)' => [1, 4032, 3024], + 'orientation 2 (flip horizontal)' => [2, 4032, 3024], + 'orientation 3 (rotate 180)' => [3, 4032, 3024], + 'orientation 4 (flip vertical)' => [4, 4032, 3024], + 'orientation 5 (transpose)' => [5, 3024, 4032], + 'orientation 6 (rotate 90 CW)' => [6, 3024, 4032], + 'orientation 7 (transverse)' => [7, 3024, 4032], + 'orientation 8 (rotate 90 CCW)' => [8, 3024, 4032], + ]; + } + + private function createJpegWithOrientation(int $width, int $height, int $orientation): string + { + $img = imagecreatetruecolor($width, $height); + ob_start(); + imagejpeg($img); + $jpeg = ob_get_clean(); + imagedestroy($img); + + $tiff = pack('A2', 'II') + . pack('v', 42) + . pack('V', 8) + . pack('v', 1) + . pack('v', 0x0112) + . pack('v', 3) + . pack('V', 1) + . pack('v', $orientation)."\x00\x00" + . pack('V', 0); + + $app1 = "Exif\x00\x00".$tiff; + $app1Segment = "\xFF\xE1".pack('n', strlen($app1) + 2).$app1; + + return substr($jpeg, 0, 2).$app1Segment.substr($jpeg, 2); + } + private function svgAsset($svg) { $asset = (new Asset) From a0d77760c4b74be51da8603e3e166ad2be693e82 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 18 May 2026 10:45:15 -0400 Subject: [PATCH 2/2] pint --- tests/Assets/AttributesTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Assets/AttributesTest.php b/tests/Assets/AttributesTest.php index f981c1f00de..b829722335c 100644 --- a/tests/Assets/AttributesTest.php +++ b/tests/Assets/AttributesTest.php @@ -192,14 +192,14 @@ private function createJpegWithOrientation(int $width, int $height, int $orienta imagedestroy($img); $tiff = pack('A2', 'II') - . pack('v', 42) - . pack('V', 8) - . pack('v', 1) - . pack('v', 0x0112) - . pack('v', 3) - . pack('V', 1) - . pack('v', $orientation)."\x00\x00" - . pack('V', 0); + .pack('v', 42) + .pack('V', 8) + .pack('v', 1) + .pack('v', 0x0112) + .pack('v', 3) + .pack('V', 1) + .pack('v', $orientation)."\x00\x00" + .pack('V', 0); $app1 = "Exif\x00\x00".$tiff; $app1Segment = "\xFF\xE1".pack('n', strlen($app1) + 2).$app1;