我可以通过编程方式确定 PNG 是否为动画吗?

问题描述 投票:0回答:5

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

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

APNG之类的东西。

Bouncy ball

(它将在 Firefox 中显示动画)。

根据维基百科文章...

APNG 将后续帧隐藏在 PNG 辅助块中,这样不支持 APNG 的应用程序就会忽略它们,但格式不会发生任何更改,以允许软件区分动画和非动画图像。

这是否意味着无法通过代码确定 PNG 是否为动画?

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

php image png apng
5个回答
18
投票

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
)。

这段代码(source)可以解决这个问题:

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

6
投票

AFAIK,不支持 APNG 的库将只获取 PNG 的第一帧。对于您的情况,您可以从 APNG(或 PNG、JPEG 等)创建一个新图像,然后将其重新保存为 PNG。如果使用 GD,它应该剥离动画数据,除非库已更新为支持 APNG。


3
投票

我想建议一个更优化的版本,它不会读取整个文件,因为这些文件可能很大,并且仍然依赖 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 字可能会被分割在两个读取块之间。


2
投票

如果任何 JS 编码员在这里遇到困难 - Javascript 版本 https://stackoverflow.com/a/4525194/3560398

const identifyApng = (byteString) => {
  if (byteString.length > 0) {
    const idatPos = byteString.indexOf('IDAT')
    if(byteString.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0) {
      return true
    }
  }
  return false
}

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;
}
© www.soinside.com 2019 - 2024. All rights reserved.