Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions src/Image/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public function crop(int $width, int $height, string $gravity = Image::GRAVITY_C
$x = intval($x);
$y = intval($y);

if ($this->image->getImageFormat() == 'GIF') {
if ($this->image->getNumberImages() > 1) {
$this->image = $this->image->coalesceImages();

foreach ($this->image as $frame) {
Expand All @@ -188,18 +188,14 @@ public function crop(int $width, int $height, string $gravity = Image::GRAVITY_C
$frame->cropImage($width, $height, $x, $y);
$frame->thumbnailImage($width, $height);
}
}

$this->image->deconstructImages();
} else {
foreach ($this->image as $frame) {
if ($gravity === self::GRAVITY_CENTER) {
$this->image->cropThumbnailImage($width, $height);
} else {
$this->image->scaleImage($resizeWidth, $resizeHeight, false);
$this->image->cropImage($width, $height, $x, $y);
}
$frame->setImagePage($width, $height, 0, 0);
}
} elseif ($gravity === self::GRAVITY_CENTER) {
$this->image->cropThumbnailImage($width, $height);
} else {
$this->image->scaleImage($resizeWidth, $resizeHeight, false);
$this->image->cropImage($width, $height, $x, $y);
}
$this->height = $height;
$this->width = $width;
Expand Down
86 changes: 86 additions & 0 deletions tests/Image/ImageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -859,4 +859,90 @@ public function test_gif_small_last_frame(): void

\unlink($target);
}

/**
* Animated WebP stores delta/partial frames. Cropping without coalesce
* scales those fragments independently and produces ghosting artifacts.
*/
public function test_crop_animated_webp_preserves_frames(): void
{
$source = __DIR__.'/../resources/disk-a/anim-delta.webp';
$image = new Image(\file_get_contents($source) ?: '');
$target = __DIR__.'/anim-delta-32x32.webp';

$image->crop(32, 32);
$image->save($target, 'webp', 100);

$this->assertFileExists($target);
$this->assertNotEmpty(\file_get_contents($target));

$output = new \Imagick($target);
$this->assertGreaterThan(1, $output->getNumberImages());
$this->assertTrue(\in_array($output->getImageFormat(), ['PAM', 'WEBP'], true));

$coalesced = $output->coalesceImages();
foreach ($coalesced as $frame) {
$this->assertEquals(32, $frame->getImageWidth());
$this->assertEquals(32, $frame->getImageHeight());
}

// Frame 0 has a red square near the top-left; after a correct resize
// that region must stay red — not blended with later frames.
$coalesced->setFirstIterator();
$pixel = $coalesced->getImagePixelColor(8, 8)->getColor();
$this->assertGreaterThan(200, $pixel['r']);
$this->assertLessThan(100, $pixel['g']);
$this->assertLessThan(100, $pixel['b']);

// Frame 1 replaces that region with green on a correct crop.
$coalesced->nextImage();
$pixel = $coalesced->getImagePixelColor(8, 8)->getColor();
$this->assertLessThan(100, $pixel['r']);
$this->assertGreaterThan(180, $pixel['g']);
$this->assertLessThan(150, $pixel['b']);

\unlink($target);
}

/**
* Consecutive identical frames are hold/pause frames. Cropping must keep
* total playback delay — deconstructImages() + WebP encode can zero it out.
*/
public function test_crop_animated_webp_preserves_hold_frames(): void
{
$sequence = new \Imagick;
foreach (['#ff0000', '#ff0000', '#0000ff'] as $color) {
$frame = new \Imagick;
$frame->newImage(40, 40, new \ImagickPixel($color));
$frame->setImageDelay(40);
$frame->setImageDispose(\Imagick::DISPOSE_NONE);
$frame->setImageFormat('gif');
$sequence->addImage($frame);
}

$blob = $sequence->getImagesBlob();
$this->assertNotFalse($blob);

$image = new Image($blob);
$image->crop(20, 20);
$outputBlob = $image->output('webp', 100);
$this->assertNotFalse($outputBlob);
$this->assertNotNull($outputBlob);

$output = new \Imagick;
$output->readImageBlob($outputBlob);

$totalDelay = 0;
foreach ($output as $frame) {
$totalDelay += $frame->getImageDelay();
}
$this->assertEquals(120, $totalDelay);
$this->assertGreaterThanOrEqual(2, $output->getNumberImages());

$coalesced = $output->coalesceImages();
foreach ($coalesced as $frame) {
$this->assertEquals(20, $frame->getImageWidth());
$this->assertEquals(20, $frame->getImageHeight());
}
}
}
Binary file added tests/resources/disk-a/anim-delta.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading