Skip to content

Fix transparent seam in GD RotateModifier - #1550

Merged
olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:bugfix/gd-rotate-transparent-seam
Oct 10, 2026
Merged

olivervogel merged 1 commit into
Intervention:developfrom
nlemoine:bugfix/gd-rotate-transparent-seam

Conversation

@nlemoine

@nlemoine nlemoine commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

An oblique rotate() on an opaque background leaves a seam of transparent pixels along the edges of the rotated image with the GD driver:

$image = ImageManager::usingDriver(GdDriver::class)->decodePath('tests/resources/test.jpg');
$image->rotate(45, 'ff0000');
// bundled GD: 899 fully transparent pixels, 1086 at 10 degrees
// libgd 2.3.3: 565 partly transparent pixels, 159 fully and 464 partly at 10 degrees

Imagick gives 0. With a white background the seam is there too, it only shows once the image is on something else than white.

imagerotate() filled the new areas with a transparent color, then the modifier drew a transparent cutout polygon on the background canvas and copied the rotated image over it with blending. imagefilledpolygon() does not cover exactly the same pixels as imagerotate(), so along the edges the transparent fill stays inside the polygon.

imagerotate() now gets the background color as fill and the result is copied without blending, so the cutout is gone. On libgd 2.3.3 the edges are also interpolated against the background now, like with Imagick.

Frames with a transparent color are first copied to a truecolor image without one. On libgd 2.3.3, at oblique angles, imagerotate() replaces the pixels of the transparent color with its fill color, so a GIF or indexed PNG would lose its transparency with the new fill.

This also fixes a TypeError on frames with a full 256 color palette: imagecolorallocatealpha() returned false there and it went straight to imagerotate().

Tests cover the seam, a transparent color at an oblique angle and a full palette. Bundled GD leaves the transparent pixels of tile.png and blocks.png at alpha 126 instead of 127 after the rotation (same on develop), so that test counts alpha >= 120.

The background was painted on the new canvas and a transparent cutout
polygon drawn where the rotated image goes, while imagerotate() filled
the new areas with a transparent color. The polygon does not match the
area covered by imagerotate() exactly, so a seam of transparent pixels
was left along the edges of the rotated image on opaque backgrounds.

Let imagerotate() fill the new areas with the background color and copy
the result without blending, which makes the cutout unnecessary.

libgd fills pixels of the transparent color with this color when
rotating, so images with a transparent color (GIF, indexed PNG) are
first copied to a truecolor image without one. The background is no
longer allocated in the frame palette, which returned false on a full
palette and made imagerotate() throw a TypeError.
@olivervogel

Copy link
Copy Markdown
Member

Thanks.

@olivervogel
olivervogel merged commit 765c15a into Intervention:develop Oct 10, 2026
8 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