2

I am trying to combine variable length PNG strips into a single GIF using the PHP GD and imagemagick libraries.

For example:

first strip = 4 frames

second strip = 8 frames

third strip = 9 frames

fourth strip = 12 frames

enter image description here = 36 frames

When combining these into one GIF there has to be enough frames for the longest animation, however the shorter animations do not have enough frames.

Right now, if there's not enough frames I just repeat the first frame, however as you would expect this leads to the animations stalling until they all restart:

combined

The number of frames isn't totally random, however it's not always as clean as the examples. There are sets with a couple with 25 frames and the highest 36, and some with frames of 1, 3, 4, 9, 5, 12, 16, 26, etc.

The reasoning for wanting this is to combine all the strips into one big image that showcases a set of strips (with information about the sets themselves on it).

Is there a way of tackling this that I am missing? Or am I best just trying to double the strips until they're all at the highest frame count and removing a few frames here and there and hope no one notices?

Here's a MCVE that shows what I am doing now:

<?php
$pngStrips = [
    "happy" => "https://i.stack.imgur.com/oq3Ex.png",
    "shocked" => "https://i.stack.imgur.com/AeoEs.png",
    "mk" => "https://i.stack.imgur.com/Ka6ho.png",
    "dance" => "https://i.stack.imgur.com/lZan3.png",
    "bored" => "https://i.stack.imgur.com/JiIN9.png"
];

$height = 130 * ceil(count($pngStrips) / 6);
$width = 150 * count($pngStrips);

// Set up the showase background gif (simplified to blank white for MCVE)
$background = imagecreatetruecolor($width, $height);
imagealphablending($background, true);
imagefill($background, 0, 0, imagecolorallocatealpha($background, 255, 255, 255, 0));

// Download from external source and split into frames and resize
$highestFrames = 0;
foreach($pngStrips as $name => $URL){    
    file_put_contents("./mcve/" . $name . ".png", file_get_contents($URL));

    $dim = getimagesize("./mcve/" . $name . ".png");

    exec("convert -crop " . $dim[1] . "x" . $dim[1] . " ./mcve/" . $name . ".png ./mcve/" . $name . "_frame_%02d.png");
    exec("convert ./mcve/" . $name . "_frame_*.png -thumbnail 90x90^ -gravity center -extent 90x90 ./mcve/" . $name . "_frame_%02d_resized.png");

    $frames = $dim[0] / $dim[1];

    if($frames > $highestFrames) $highestFrames = $frames;
}

// Every frame of the main gif
for($i = 0; $i < $highestFrames; $i++ ){
    $xcoord = 20;
    $ycoord = 5;
    $count = 1;

    // Make a new frame
    $frame = imagecrop($background, array('x'=>0,'y'=>0,'width'=>imagesx($background),'height'=>imagesy($background)));

    // Add the appropriate frame to this frame of the main gif
    foreach($pngStrips as $name => $URL) {
        $frameFile = "./mcve/" . $name . "_frame_" . sprintf("%02d", $i) . "_resized.png";
        if(!file_exists($frameFile)){
            $frameFile = "./mcve/" . $name . "_frame_00_resized.png";
        }

        $powerImage = imagecreatefrompng($frameFile);

        imagecopymerge($frame, $powerImage, $xcoord, $ycoord, 0, 0, 90, 90, 100);

        $xcoord += 145;

        if($count % 6 == 0){
            $xcoord = 20;
            $ycoord += 130;
        }
        $count++;
    }

    // Save frame
    imagepng($frame, "./mcve/final_frame_" . sprintf("%02d", $i) . ".png");
}

// Into 1 big gif
exec("convert -loop 0 ./mcve/final_frame_*.png ./mcve/final.gif");

header('Content-Type: image/gif');
echo file_get_contents("./mcve/final.gif");
Daniel
  • 1,229
  • 14
  • 24

1 Answers1

3

The best solution (but the image can be huge) is to use the lowest common multiple of all the frame counts (72 in this case, so not that much), and repeat each image sequence as many times as necessary (18,9,8,6,2).

xenoid
  • 8,396
  • 3
  • 23
  • 49