Php - Imagick Optimize Gif Manually
New here? Learn about Bountify and follow @bountify to get notified of new bounties! x

Bounty: Create a function that manually optimizes a gif animation by deleting repeated content and replacing it with transparency, comparing frame by frame.

I already created all of the base code, I need you to complete the optimization function. Gifsicle and optimizeImageLayers() don't optimize well, so I need a hand-coded solution.

I already created the pseudo code for the solution under "TODO". Please transform it into real code using Imagick functions.

Environment: PHP 5.6 using Imagick extension

class GifAnimation{

/**
 * @var Imagick
 */

private $animation;
/**
 * @var array of Imagick
 */

private $imagesArray;

function __construct(){
    $this->animation =  new \Imagick();
    $this->animation->setFormat("GIF");
    $this->imagesArray = array(); 
}

function __destruct(){
    if($this->imagesarray){
        foreach($this->imagesArray as $image){
            $image->destroy();
        }
    }
}

/**
 * @param Imagick $image
 * @param int $delay 
 */
public function add($image,   $delay=100){
    //If this is not the first image, optimize it by comparing to all previous images.
    if(count($imagesArray)>1){
        $image = self::__optimizeFrame($image, $this->imagesArray);
    }

    $this->imagesArray[] = $image;


    //Add images to the animation
    $this->animation->addImage($image);
    $this->animation->setImageDelay($delay);
    $this->animation->nextImage();

}



  /** 
 * @param Imagick $image
 * @param array of Imagick $imagesArray 
 */
private static function __optimizeFrame($imageNewFrame, $imagesArray){
    /**
    ** //TODO: Implement this function. Pseudocode:
    ** //1. Create a composite of all images found in $this->imagesArray. (Composite them one over the other, in order, putting the oldest at the bottom and the newest at the top).
    ** //This result will be called $compositeImageOfPreviousFrames.
    **
    **  //Something like this:
    **  if($this->imagesArray){
    **      foreach($this->imagesArray as $image){
    **          $compositeImageOfPreviousFrames = composite(0,0, $compositeImageOfPreviousFrames, $image);
    **      }
    ** }
    **
    **
    ** //2. Compare $imageNewFrame to $compositeImageOfPreviousFrames and discard pixels that are the same and change to transparent.
    ** //3. Save result from previous process as $imageDiff and return $imageDiff
    ** 
    ** With this algorithm each new frame should only contain the changes that are necessary to portray the overall animation
    **
    **/     

}

IMPORTANT: If you can solve this "simple" Imagick problem, I will award the bounty to you. I can do the rest.

Frame 1: http://www.fmwconcepts.com/misc_tests/diff_images/cyclops.png

Frame 2: http://www.fmwconcepts.com/misc_tests/diff_images/cyclops_question.png

The optimized version of frame 2 should be http://www.fmwconcepts.com/misc_tests/diff_images/cyclops_sep.png but conserving the canvas size (and filling it with a transparent background).

If you can achieve an Imagick function that given frame 1 and frame 2, it gives that result, the bounty is yours.

Hi, do you have an example of gif to optimize and optimized? Se we can compare results. Maybe it can be solved through the Duff Porter Alpha Composition Methods, see: which are accessible in Imagick through the compositeImage() method.
cueball 27 days ago
Hi Cueball, thanks so much for working on this, I also believe that Duff Porter Alpha Composition methods could be the solution. All of the frames/images of the gif do NOT contain transparency and all have a white-based background. I will try to provide you a sample today. This article might also be helpful: http://www.imagemagick.org/discourse-server/viewtopic.php?t=15584 . This approach gets the "difference" image between two, but it doesn't keep the original canvas size (which would be a requirement for us).
georgefountain 27 days ago
Cueball, here is an example of an unoptimized gif that uses non-transparent images per frame: https://www.creativetechs.com/wp-content/uploads/2009/03/StickmanAnnimation.gif . The goal would be to for each frame to just store the changes in transparent images instead of the full frame. Of course if something needs to be "erased" it would also need to include a bit of background for those areas. Let me know what you think...
georgefountain 26 days ago
Cueball, there are two days left... do you think you can fix this? I will award you bounty and 100% extra tip if solved. Thank you!
georgefountain 25 days ago
Is there anyone who can solve this? Bounty increased...
georgefountain 24 days ago
@georgefountain Can we have one gif that is not optimised and another that is optimised so that we can compare results
mashtullah 22 days ago
Hi Mastullah! Thanks for your interest in this task. I cannot generate an optimized gif by hand. So I have no way of providing an optimized gif, unfortunately. If you can develop just an imagick script that compares two images and gets the difference, while preserving the original canvas size, I can workout the rest and I will award you the bounty. This would be a starting point : http://www.imagemagick.org/discourse-server/viewtopic.php?t=15584 . Please let me know if you have any other questions or if I can collaborate with you further. Thanks!
georgefountain 22 days ago
I added further instrucitons on the bounty, and reduced the problem to something simpler. If you can solve just that, the bounty is yours. Thanks!
georgefountain 22 days ago
awarded to slr

Crowdsource coding tasks.

1 Solution

Winning solution

looks like $image->compositeImage($imagePrevious, Imagick::COMPOSITE_CHANGEMASK, 0, 0); is what you need here. fortunately Imagick::COMPOSITE_CHANGEMASK is available since Imagick version 3.2.0b2. it uses method that makes specific pixels in the destination image full-transparent which was added to ImageMagick version 6.3.4.

the function in question may be like this:

private static function __optimizeFrame($imageNewFrame, $imagesArray){
    // FIXME store $compositeImageOfPreviousFrames as private or global to not do it for each frame
    if($this->imagesArray){
        foreach($this->imagesArray as $image){
            $compositeImageOfPreviousFrames->compositeImage($image, Imagick::COMPOSITE_DEFAULT, 0, 0);
            // or COMPOSITE_COPY or COMPOSITE_SRCATOP instead of COMPOSITE_DEFAULT
            // I'm not sure which one is faster (also you need to watch semi-transparent pixels)
        }
    }

    $imageNewFrame->compositeImage($compositeImageOfPreviousFrames, Imagick::COMPOSITE_CHANGEMASK, 0, 0);

    return $imageNewFrame;
}

example code to test on http://phpfiddle.org (they have Imagick enabled):

<?php

$imgBase = new Imagick("http://www.fmwconcepts.com/misc_tests/diff_images/cyclops.png");
$img = new Imagick("http://www.fmwconcepts.com/misc_tests/diff_images/cyclops_question.png");

$img->compositeImage($imgBase, Imagick::COMPOSITE_CHANGEMASK, 0, 0);

header("Content-Type: image/png");
echo $img;

?>

but after all I believe you need to check ImageMagick built-in transparency optimization functionality.

Finally someone who solved it! Thank you so much slr! That filter did the magic!
georgefountain 18 days ago
@georgefountain you're welcome
slr 18 days ago