48

I'm currently running into some issues resizing images using GD.

Everything works fine until i want to resize an animated gif, which delivers the first frame on a black background.

I've tried using getimagesize but that only gives me dimensions and nothing to distinguish between just any gif and an animated one.

Actual resizing is not required for animated gifs, just being able to skip them would be enough for our purposes.

Any clues?

PS. I don't have access to imagemagick.

Kind regards,

Kris

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
Kris
  • 40,604
  • 9
  • 72
  • 101
  • The author said he doesn't have ImageMagick. But for all the people search for any way to find out if an gif is animated and found their way here from Google (like me): ImageMagick does this pretty easily: http://php.net/manual/en/imagick.getimageiterations.php – Lukas Sep 29 '14 at 15:00

6 Answers6

46

While searching for a solution to the same problem I noticed that the php.net site has a follow-up to the code Davide and Kris are referring to, but, according to the author, less memory-intensive, and possibly less disk-intensive.

I'll replicate it here, because it may be of interest.

source: http://www.php.net/manual/en/function.imagecreatefromgif.php#88005

function is_ani($filename) {
    if(!($fh = @fopen($filename, 'rb')))
        return false;
    $count = 0;
    //an animated gif contains multiple "frames", with each frame having a
    //header made up of:
    // * a static 4-byte sequence (\x00\x21\xF9\x04)
    // * 4 variable bytes
    // * a static 2-byte sequence (\x00\x2C)

    // We read through the file til we reach the end of the file, or we've found
    // at least 2 frame headers
    while(!feof($fh) && $count < 2) {
        $chunk = fread($fh, 1024 * 100); //read 100kb at a time
        $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
    }

    fclose($fh);
    return $count > 1;
}
rr-
  • 14,303
  • 6
  • 45
  • 67
Martijn Heemels
  • 3,529
  • 5
  • 37
  • 38
  • 7
    A recently added note mentions that photoshop may use `\x00\x21` instead of `\x00\x2C` – Frank Farmer Jun 16 '11 at 19:24
  • 6
    Frank's note is not as outspoken as it should be. To others viewing this page, please see his full explanation at [here](http://www.php.net/manual/en/function.imagecreatefromgif.php#104473) – billmalarky Nov 22 '11 at 05:40
  • 1
    Compared to Kris' solution below, there might be a performance penalty due to the use of preg_match_all in favor of strpos. On the upside, the file is read in memory-friendly chunks, in contrast to file_get_contents. However, the solution does not cover the cornerstone of a frame marker spanning over 2 chunks, so I edited it a bit. – untill Aug 08 '14 at 13:08
  • 1
    I discovered that Paint Tool SAI and Photoshop CS6 sometimes doesn't include 00 at end of app extension block, meaning that the first `\x00` can be omitted. – rr- May 23 '15 at 07:37
19

There is a brief snippet of code in the PHP manual page of the imagecreatefromgif() function that should be what you need:

imagecreatefromgif comment #59787 by ZeBadger

TRiG
  • 10,148
  • 7
  • 57
  • 107
Davide Gualano
  • 12,813
  • 9
  • 44
  • 65
  • Weird that I didn't find that in my default mirror of the manual, but tanks a lot for the link. I modified the function and gave both the original poster and you credit in comment. – Kris Nov 11 '08 at 12:25
  • An optimized version [here](http://it1.php.net/manual/en/function.imagecreatefromgif.php#104473). – Kevin Robatel Feb 18 '16 at 10:37
  • 1
    That link is down for some reason, it can still be found here though: http://php.net/manual/en/function.imagecreatefromgif.php#104473 – John Mellor May 24 '17 at 02:16
7

Here's the working function:

/**
 * Thanks to ZeBadger for original example, and Davide Gualano for pointing me to it
 * Original at http://it.php.net/manual/en/function.imagecreatefromgif.php#59787
 **/
function is_animated_gif( $filename )
{
    $raw = file_get_contents( $filename );

    $offset = 0;
    $frames = 0;
    while ($frames < 2)
    {
        $where1 = strpos($raw, "\x00\x21\xF9\x04", $offset);
        if ( $where1 === false )
        {
            break;
        }
        else
        {
            $offset = $where1 + 1;
            $where2 = strpos( $raw, "\x00\x2C", $offset );
            if ( $where2 === false )
            {
                break;
            }
            else
            {
                if ( $where1 + 8 == $where2 )
                {
                    $frames ++;
                }
                $offset = $where2 + 1;
            }
        }
    }

    return $frames > 1;
}
Kris
  • 40,604
  • 9
  • 72
  • 101
5

This is an improvement of the current top voted answer but I don't have enough reputation to comment yet. The problem with that answer is that it reads the file in 100Kb chunks and the end of frame marker might be split in between 2 chunks. A fix for that is to add the last 20b of the previous frame to the next one:

<?php
function is_ani($filename) {
  if(!($fh = @fopen($filename, 'rb')))
    return false;
  $count = 0;
  //an animated gif contains multiple "frames", with each frame having a
  //header made up of:
  // * a static 4-byte sequence (\x00\x21\xF9\x04)
  // * 4 variable bytes
  // * a static 2-byte sequence (\x00\x2C) (some variants may use \x00\x21 ?)

  // We read through the file til we reach the end of the file, or we've found
  // at least 2 frame headers
  $chunk = false;
  while(!feof($fh) && $count < 2) {
    //add the last 20 characters from the previous string, to make sure the searched pattern is not split.
    $chunk = ($chunk ? substr($chunk, -20) : "") . fread($fh, 1024 * 100); //read 100kb at a time
    $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
  }

  fclose($fh);
  return $count > 1;
}
  • Old question, I know, but is [this](https://stackoverflow.com/questions/280658/can-i-detect-animated-gifs-using-php-and-gd#comment48907334_415942) comment taken into account? Or is it irrelevant? – Philip May 11 '22 at 22:16
2

Reading whole file with file_get_contents may take too much memory if the given file is too large. I've re-factored the function previously given which reads just enough bytes to check frames and returns as soon as it finds at least 2 frames.

<?php
/**
 * Detects animated GIF from given file pointer resource or filename.
 *
 * @param resource|string $file File pointer resource or filename
 * @return bool
 */
function is_animated_gif($file)
{
    $fp = null;

    if (is_string($file)) {
        $fp = fopen($file, "rb");
    } else {
        $fp = $file;

        /* Make sure that we are at the beginning of the file */
        fseek($fp, 0);
    }

    if (fread($fp, 3) !== "GIF") {
        fclose($fp);

        return false;
    }

    $frames = 0;

    while (!feof($fp) && $frames < 2) {
        if (fread($fp, 1) === "\x00") {
            /* Some of the animated GIFs do not contain graphic control extension (starts with 21 f9) */
            if (fread($fp, 1) === "\x2c" || fread($fp, 2) === "\x21\xf9") {
                $frames++;
            }
        }
    }

    fclose($fp);

    return $frames > 1;
}
hdogan
  • 903
  • 6
  • 6
  • +1 for being absolutely true. Back then though, the files were small and reading them in their entirety was faster than byte by byte for reasons that I did not research, or don't remember researching. – Kris Feb 13 '17 at 12:29
  • 2
    Note that `fread($fp, 1) === "\x2c" || fread($fp, 2) === "\x21\xf9"` will first read one byte (A), check it it's 0x2C, if not it will then read the two bytes **after** byte A, not after the 0x00 byte. So the matching byte strings are `00 2C` or `00 ** 21 F9` where `**` means any byte. I'm not sure if this is intended or not, but it's not very clear from the code. – Qtax Aug 03 '17 at 18:23
-1

Animated GIF must have the following string

"\x21\xFF\x0B\x4E\x45\x54\x53\x43\x41\x50\x45\x32\x2E\x30"

I've tested on a few animated gifs and it seem the string is at the pos 781 of a file (found with file_get_contents and strpos)

jmrk
  • 34,271
  • 7
  • 59
  • 74
user2731504
  • 187
  • 1
  • 3