我能用PHP和GD扩展检测动态GIF图吗?

48

目前我在使用GD调整图像大小时遇到了一些问题。

一切正常,直到我想要缩放动画gif,这将在黑色背景上提供第一帧。

我尝试使用getimagesize,但它只给出了尺寸,无法区分普通gif和动画gif。

对于动画gif,实际上不需要调整大小,只需要能够跳过它们即可满足我们的需求。

有任何线索吗?

附言:我没有访问imagemagick的权限。

此致

Kris


作者说他没有ImageMagick。但是对于所有搜索任何方法以查找gif是否为动画并从Google(像我一样)找到这里的人:ImageMagick可以很容易地实现这一点:http://php.net/manual/en/imagick.getimageiterations.php - Lukas
6个回答

46

在寻找解决相同问题的方案时,我注意到php.net网站对Davide和Kris所引用的代码进行了跟进,但是根据作者的说法,这种方法使用的内存可能更少,磁盘占用也可能更少。

因为这可能很有意思,所以我在这里复制它。

来源: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;
}

7
最近添加的注释提到,Photoshop 可能会使用"\x00\x21"而不是"\x00\x2C"。 - Frank Farmer
6
请参考这里:Frank的笔记不够直言,建议查看他在此处的完整解释:http://www.php.net/manual/en/function.imagecreatefromgif.php#104473 - billmalarky
1
与Kris在下面的解决方案相比,由于使用preg_match_all而不是strpos,可能会有性能损失。好处是文件以内存友好的块读取,而不是file_get_contents。然而,该解决方案未涵盖跨越2个块的框架标记的基石,因此我对其进行了编辑。 - untill
1
我发现Paint Tool SAI和Photoshop CS6有时候在应用程序扩展块的结尾没有包含00,这意味着第一个\x00可以省略。 - rr-

19

很奇怪我在默认的手册镜像中没有找到这个,但非常感谢提供链接。我修改了函数并在注释中给了原始帖子和你们两个人的功劳。 - Kris
这里有一个优化版本的链接:http://it1.php.net/manual/en/function.imagecreatefromgif.php#104473。 - Kevin Robatel
1
那个链接由于某些原因已经失效,但是你仍然可以在这里找到它:http://php.net/manual/en/function.imagecreatefromgif.php#104473 - John Mellor

7
这是可用的函数:
/**
 * 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;
}

5

这是对当前最受欢迎的答案的改进,但我还没有足够的声望来发表评论。那个答案的问题在于它以100Kb的块读取文件,而帧结束标记可能会分裂在两个块之间。解决方法是将前一个帧的最后20个字节添加到下一个帧中:

<?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;
}

这是一个老问题,我知道,但是是否考虑了这个评论?还是它不相关? - Philip

2

如果要读取的文件太大,使用file_get_contents读取整个文件可能会占用过多的内存。我已经重新设计了之前的函数,它只读取足够的字节来检查帧并在找到至少两个帧时立即返回。

<?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;
}

+1,因为这是绝对正确的。当时,文件很小,完整读取比逐字节读取更快,原因我没有研究过,或者不记得研究过了。 - Kris
2
请注意,语句 fread($fp, 1) === "\x2c" || fread($fp, 2) === "\x21\xf9" 首先会读取一个字节(A),然后检查它是否为 0x2C,如果不是,则会读取字节A之后的两个字节,而不是在0x00字节之后。因此匹配的字节字符串为00 2C00 ** 21 F9,其中**表示任意字节。我不确定这是否是有意为之的,但从代码中并不十分清晰。 - Qtax

-1

动画 GIF 必须具有以下字符串

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

我已在几个动画 GIF 上进行了测试,发现字符串位于文件的位置 781(使用 file_get_contents 和 strpos 找到)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接