我可以通过编程方式确定PNG是否已设置动画?

时间:2010-12-24 08:37:16

标签: php image png apng

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

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

APNG之类的东西。

Bouncy ball

(它将在Firefox中设置动画)。

根据Wikipedia article ...

  

APNG隐藏PNG辅助组块中的后续帧,使得APNG不知道的应用程序会忽略它们,但是格式没有改变,以允许软件区分动画和非动画图像。

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

如果有可能,请指点我正确的方向PHP(GD,ImageMagick)?

4 个答案:

答案 0 :(得分:15)

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

答案 1 :(得分:5)

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

答案 2 :(得分:0)

我想建议一个更优化的版本,它不会读取整个文件,因为它们可能很大,并且在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倍或更多,并且占用的内存更少。

NB:这可以通过读取的大小或将前一个块与当前块串联来进一步调整。顺便说一下,我们需要这种连接,因为acTL / IDAT字可能会在两个读取的块之间分割。

答案 3 :(得分:0)

这是我的函数,它扫描块结构,而不仅仅是文件内的子字符串(如果 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;
}