【问题标题】:Can I detect animated gifs using php and gd?我可以使用 php 和 gd 检测动画 gif 吗?
【发布时间】:2010-09-21 19:02:44
【问题描述】:

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

一切正常,直到我想调整动画 gif 的大小,它在黑色背景上提供第一帧。

我尝试过使用getimagesize,但这只会给我提供尺寸,无法区分任何 gif 和动画。

动画 gif 不需要实际调整大小,只需能够跳过它们就足以满足我们的目的。

有什么线索吗?

PS。我无权访问 imagemagick。

亲切的问候,

克里斯

【问题讨论】:

  • 作者说他没有ImageMagick。但是对于所有寻找任何方法来确定 gif 是否是动画并从谷歌(像我一样)找到他们的方式的人:ImageMagick 很容易做到这一点:php.net/manual/en/imagick.getimageiterations.php

标签: php gd


【解决方案1】:

imagecreatefromgif() 函数的 PHP 手册页中有一段简短的 sn-p 代码,应该是您需要的:

imagecreatefromgif comment #59787 by ZeBadger

【讨论】:

  • 奇怪的是,我没有在我的默认手册镜像中找到它,但链接很多。我修改了功能,并在评论中给出了原始海报和您的信用。
  • 优化版here.
  • 该链接由于某种原因已关闭,但仍可以在此处找到:php.net/manual/en/function.imagecreatefromgif.php#104473
【解决方案2】:

这是工作函数:

/**
 * 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;
}

【讨论】:

    【解决方案3】:

    在寻找相同问题的解决方案时,我注意到 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;
    }
    

    【讨论】:

    • 最近添加的注释提到 Photoshop 可能使用 \x00\x21 而不是 \x00\x2C
    • 弗兰克的说明并不像应有的那样直言不讳。对于查看此页面的其他人,请在here 上查看他的完整说明
    • 与下面 Kris 的解决方案相比,由于使用 preg_match_all 来支持 strpos,可能会导致性能下降。从好的方面来说,与 file_get_contents 相比,文件是以内存友好的块读取的。但是,该解决方案并未涵盖跨越 2 个块的帧标记的基石,因此我对其进行了一些编辑。
    • 我发现 Paint Tool SAI 和 Photoshop CS6 有时在应用扩展块末尾不包含 00,这意味着第一个 \x00 可以省略。
    【解决方案4】:

    动画 GIF 必须有以下字符串

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

    我已经测试了一些动画 gif,似乎该字符串位于文件的 pos 781(通过 file_get_contents 和 strpos 找到)

    【讨论】:

      【解决方案5】:

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

      <?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 表示绝对真实。不过当时,这些文件很小,而且由于我没有研究过或不记得研究过的原因,它们的整体读取速度比逐字节快。
      • 注意fread($fp, 1) === "\x2c" || fread($fp, 2) === "\x21\xf9"会先读取一个字节(A),检查它是0x2C,如果不是,它会读取之后字节A之后的两个字节,而不是之后0x00 字节。所以匹配的字节字符串是00 2C00 ** 21 F9 其中** 表示任何字节。我不确定这是不是有意的,但从代码中不是很清楚。
      【解决方案6】:

      这是对当前投票最多的答案的改进,但我还没有足够的声誉发表评论。该答案的问题在于它以 100Kb 块读取文件,并且帧结束标记可能被分成 2 个块。解决方法是将前一帧的最后 20b 添加到下一帧:

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

      【讨论】:

        猜你喜欢
        • 2011-01-10
        • 2017-12-25
        • 2012-06-17
        • 2012-11-27
        • 1970-01-01
        • 2011-02-26
        • 2011-03-09
        • 1970-01-01
        • 2010-10-14
        相关资源
        最近更新 更多