Skip to content

Reset page geometry after Imagick rotation#1505

Merged
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:bugfix/imagick-rotate-page-offset
Jul 7, 2026
Merged

Reset page geometry after Imagick rotation#1505
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:bugfix/imagick-rotate-page-offset

Conversation

@nlemoine

@nlemoine nlemoine commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Rotating an image with the Imagick driver leaves a page offset on every frame. You don't notice it on a still image. On an animated one that then gets encoded to AVIF, it breaks: the bottom-right of every frame comes out transparent.

Where it comes from: rotateImage() at a non-right angle grows the canvas and sets a negative page offset. A 480x480 frame rotated 45 degrees ends up with page 680x680-100-100. The single-frame AVIF encoder and the animated WebP encoder ignore that offset. The animated AVIF path does not, it composites every frame shifted up and to the left.

The real bug is in ImageMagick's animated AVIF writer, not in Intervention. But we can shield against it here, and the other modifiers already do this (see the fix below).

Repro with plain magick, no PHP involved:

magick in.gif -coalesce -background '#3b82f6' -rotate 45 bad.avif
magick 'bad.avif[0]' -alpha on -format '%[pixel:p{679,679}]' info:
# srgba(0,0,0,0)  -> transparent

magick in.gif -coalesce -background '#3b82f6' -rotate 45 +repage good.avif
magick 'good.avif[0]' -alpha on -format '%[pixel:p{678,678}]' info:
# srgba(59,130,246,1)  -> the background color, correct

Same thing through the library:

use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\Encoders\AvifEncoder;

$image = ImageManager::usingDriver(Driver::class)
    ->decodePath('in.gif')
    ->rotate(45, '#3b82f6');

$image->encode(new AvifEncoder());
// every frame is 680x680, bottom-right corner is transparent

Fix: reset each frame's page to +0+0 right after rotateImage(). TrimModifier and CoverModifier already do this after they change the canvas, so the rotate modifier is just catching up with them. GD and vips don't leave an offset after a rotate, so this also lines the drivers up.

One line in Drivers/Imagick/Modifiers/RotateModifier.php:

$frame->native()->setImagePage(0, 0, 0, 0);

Test: I added testRotateResetsFramePageOffset next to the existing rotate test. It rotates an animated fixture by 45 and checks every frame has offset 0. It fails before the change and passes after.

Corner pixel, same input, before and after:

driver animated AVIF, bottom-right corner
imagick, before transparent (srgba 0,0,0,0)
imagick, after background color (srgba 59,130,246,1)
gd fine (it flattens the animation to one frame)
vips fine (no page offset)

Two things if you want to reproduce it:

It only shows with real photo-like frames. A flat color or a tiny synthetic gif compresses to trivial tiles and the corner stays fine, so use a real animated image.

You need an ImageMagick build with animated AVIF support. I saw it on 7.1.2-26, and it was also reported on a couple of earlier 7.1.2 builds. Older ImageMagick without animated AVIF is not affected.

Imagick's rotateImage() grows the virtual canvas for non-right angles and
leaves each frame with a negative page offset (a 480x480 frame rotated 45
degrees ends up 680x680-100-100). ImageMagick's animated-AVIF writer
(libheif sequences) honours that offset when compositing the sequence,
shifting every frame up and left and leaving the bottom/right region
transparent.

Reset each frame's page to +0+0 after rotating, mirroring TrimModifier
and CoverModifier which already normalize page geometry. This keeps the
Imagick driver consistent with the GD and vips drivers, which leave no
offset, and fixes the transparent-corner artifact in animated AVIF.
@olivervogel

Copy link
Copy Markdown
Member

Thank you.

@olivervogel olivervogel merged commit 81d1cbd into Intervention:develop Jul 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants