Imagick / imagick

🌈 The Imagick PHP extension 🌈
http://pecl.php.net/imagick
Other
540 stars 136 forks source link

Foreach shenanigans #122

Open Danack opened 8 years ago

Danack commented 8 years ago

Doing a foreach over an image with multiple internal images (e.g. an animated gif) does not do what anyone would expect. e.g.

foreach ($animatedGifImagick as $frameImagick) {
    // $frameImagick is not a separate object. Instead it is the same MagickWand as animatedGifImagick but
   // with the internal iterator set to the appropriate frame number.
}

This causes a problem when anyone uses $frameImagick with an operation that is 'multiple-frame-aware'. As it is still part of an image with multiple sub-images. So adding it to another image will add multiple images.

http://stackoverflow.com/questions/33562725/clone-blur-crop-and-composite-animated-gif/33585825#33585825

Danack commented 8 years ago

Copying and pasting for posterity


It looks like there is a 'bug'* in the foreach implementation of Imagick. When you do this:

foreach ($animationImagick as $frameImagick) {
    //whatever
}

The $frameImagick object isn't actually a single frame, instead it's a copy of the whole animation with the internal iterator set to the appropriate frame for the animation.

Because of this, when you go to composite that 'frame image' back into the original image, ImageMagick sees that it's an image with multiple internal images and resets the internal iterator again. This leads to the first frame being pasted all the time.

Explicitly separating the frame image from the animation set of images solves this problem. Additionally not modifying the data you're iterating over is a sensible thing to do. This code does both

$image = new Imagick("./SeriouslyBurned.gif");
$image = $image->coalesceImages(); 
$newImageFrames = [];
$numberImages = $image->getNumberImages();
$outputImage = new Imagick();

foreach ($image as $frame) {
    $frame = $frame->getImage();

    $blurred = clone $frame;
    $blurred->cropImage(360, 180, 241, 60);
    $blurred->blurImage(5,3);
    $frame->compositeImage($blurred, imagick::COMPOSITE_OVER, 241, 60);

    $blurred = clone $frame;
    $blurred->cropImage(320, 20, 0, 0);
    $blurred->blurImage(5,3);
    $frame->compositeImage($blurred, imagick::COMPOSITE_OVER, 0, 0);    

    $blurred = clone $frame;
    $blurred->cropImage(320, 20, 0, 160);
    $blurred->blurImage(5,3);
    $frame->compositeImage($blurred, imagick::COMPOSITE_OVER, 0, 160);

    $outputImage->addImage($frame);
}

$outputImage->setImageFormat('gif');
$outputImage->writeImages("output_file.gif", true);

*It's probably technically not a bug...as the code is doing what it was told to do. It's just not what anyone really wants....