【问题标题】:Can I programmatically determine if a PNG is animated?我可以以编程方式确定 PNG 是否为动画?
【发布时间】:2011-05-30 08:04:43
【问题描述】:

我将 PNG(以及 JPEG)图像上传到我的网站。

它们应该是静态的(即一帧)。

有APNG这样的东西。

(它将在 Firefox 中进行动画处理)。

根据Wikipedia article...

APNG 以这样一种方式隐藏 PNG 辅助块中的后续帧,使得不识别 APNG 的应用程序会忽略它们,但不会更改格式以允许软件区分动画和非动画图像。

这是否意味着无法确定 PNG 是否使用代码进行动画处理?

如果可能的话,请您指出正确的 PHP 方向(GD,ImageMagick)吗?

【问题讨论】:

  • 直到你的问题,我不知道动画 png 文件。谢谢。

标签: php image png apng


【解决方案1】:

这是我的函数,它扫描块结构,而不仅仅是文件中的子字符串(如果 acTL 子字符串出现在元数据中而不是块名称中,以防止误报)。 为简单起见,我使用了 SplFileObject,直接使用 fopen/fread/fclose 可以提高速度。

function is_apng(string $filename): bool
{
    $f = new \SplFileObject($filename, 'rb');
    $header = $f->fread(8);
    if ($header !== "\x89PNG\r\n\x1A\n") {
        return false;
    }
    while (!$f->eof()) {
        $bytes = $f->fread(8);
        if (strlen($bytes) < 8) {
            return false;
        }
        $chunk = unpack('Nlength/a4name', $bytes);
        switch ($chunk['name']) {
            case 'acTL':
                return true;
            case 'IDAT':
                return false;
        }
        $f->fseek($chunk['length'] + 4, SEEK_CUR);
    }
    return false;
}

【讨论】:

    【解决方案2】:

    我想建议一个更优化的版本,它不会读取整个文件,因为这些文件可能很大,并且仍然依赖于 IDAT 规则之前的 acTL:

    function identify_apng($filepath) {
        $apng = false;
    
        $fh = fopen($filepath, 'r');
        $previousdata = '';
        while (!feof($fh)) {
            $data = fread($fh, 1024);
            if (strpos($data, 'acTL') !== false) {
                $apng = true;
                break;
            } elseif (strpos($previousdata.$data, 'acTL') !== false) {
                $apng = true;
                break;
            } elseif (strpos($data, 'IDAT') !== false) {
                break;
            } elseif (strpos($previousdata.$data, 'IDAT') !== false) {
                break;
            }
    
            $previousdata = $data;
        }
    
        fclose($fh);
    
        return $apng;
    }
    

    速度从 5 倍提高到 10 倍或更多,具体取决于文件的大小,而且它使用的内存也少很多。

    注意:这可能可以通过给 fread 的大小或前一个块与当前块的连接来进行更多调整。顺便说一句,我们需要这种连接,因为 acTL/IDAT 字可能会在两个读取块之间拆分。

    【讨论】:

      【解决方案3】:

      APNG 图像旨在为不支持它们的读者“伪装”为 PNG。也就是说,如果阅读器不支持它们,它只会假设它是一个普通的 PNG 文件并只显示第一帧。这意味着它们具有与 PNG (image/png) 相同的 MIME 类型,它们具有相同的幻数 (89 50 4e 47 0d 0a 1a 0a),并且通常它们以相同的扩展名保存(尽管这并不是一个真正的检查方法文件类型)。

      那么,你如何区分它们呢? APNG 在其中有一个“acTL”块。所以,如果你搜索字符串acTL(或者,在十六进制中,61 63 54 4C(块标记之前的4个字节(即00 00 00 08)是大端格式的块大小,不计算大小,标记,或字段末尾的 CRC32))你应该很好。为了让它更好,请检查该块是否出现在“IDAT”块的第一次出现之前(只需查找IDAT)。

      这段代码(取自http://foone.org/apng/identify_apng.php)可以解决问题:

      <?php
      # Identifies APNGs
      # Written by Coda, functionified by Foone/Popcorn Mariachi#!9i78bPeIxI
      # This code is in the public domain
      # identify_apng returns:
      # true if the file is an APNG
      # false if it is any other sort of file (it is not checked for PNG validity)
      # takes on argument, a filename.
      function identify_apng($filename)
          {
          $img_bytes = file_get_contents($filename);
          if ($img_bytes)
              {
              if(strpos(substr($img_bytes, 0, strpos($img_bytes, 'IDAT')), 
                       'acTL')!==false)
                  {
              return true;
              }
              }
          return false;
          }
      ?>
      

      【讨论】:

      • 太棒了!虽然当然,我宁愿只是return那个条件:) +1
      • @alex:当然,那样会更好。我只是照原样复制代码(我不是作者);)
      • @MarcusJ 与其留下粗鲁的 cmets,不如继续编辑我的答案以使其更好。
      • 给定的链接坏了,感谢上帝你把它复制到了这里。这就是为什么你不应该只是链接;)
      【解决方案4】:

      AFAIK,不支持 APNG 的库只会占用 PNG 的第一帧。在您的情况下,您可以从 APNG(或 PNG、JPEG 等)创建一个新图像并将其重新保存为 PNG。如果使用 GD,它应该去除动画数据,除非库已更新为支持 APNG。

      【讨论】:

      • 我没有考虑到这一点——我所有的图像都是通过 GD 处理的。这是一个很好的建议。 +1
      猜你喜欢
      • 2019-01-29
      • 1970-01-01
      • 1970-01-01
      • 2013-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 1970-01-01
      相关资源
      最近更新 更多