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

发布于 2024-10-08 21:37:21 字数 528 浏览 9 评论 0原文

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

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

有这样的东西 APNG

Bouncy ball

(它将在 Firefox 中呈现动画)。

根据 维基百科文章...

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

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

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

I have PNG (as well as JPEG) images uploaded to my site.

They should be static (i.e. one frame).

There is such thing as APNG.

Bouncy ball

(it will be animated in Firefox).

According to the Wikipedia article...

APNG hides the subsequent frames in PNG ancillary chunks in such a way that APNG-unaware applications would ignore them, but there are otherwise no changes to the format to allow software to distinguish between animated and non-animated images.

Does this mean it is impossible to determine if a PNG is animated with code?

If it is possible, can you please point me in the right direction PHP wise (GD, ImageMagick)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

遇见了你 2024-10-15 21:37:21

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

APNG images are designed to be "camouflaged" as PNG for readers that not support them. That is, if a reader does not support them, it will just assume it is a normal PNG file and display only the first frame. That means that they have the same MIME type as PNG (image/png), they have the same magic number (89 50 4e 47 0d 0a 1a 0a) and generally they're saved with the same extension (although that is not really a good way to check for a file type).

So, how do you distinguish them?
APNG have a "acTL" chunk in them. So, if you search for the string acTL (or, in hex, 61 63 54 4C (the 4 bytes before the chunk marker (i.e. 00 00 00 08) are the size of the chunk in big endian format, without counting the size, marker, or CRC32 at the end of the field)) you should be pretty good. To get it even better, check that this chunk appears before the first occurrence of the "IDAT" chunk (just look for IDAT).

This code (taken from http://foone.org/apng/identify_apng.php ) will do the trick:

<?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;
    }
?>
∞琼窗梦回ˉ 2024-10-15 21:37:21

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

AFAIK, libraries that do not support APNG will just take the first frame of the PNG. In your case, you could just create a new image from the APNG (or PNG, JPEG, etc.) and re-save it as PNG. It should strip the animation data if using GD, unless the library's been updated to support APNG.

人海汹涌 2024-10-15 21:37:21

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

I'd like to suggest a more optimised version, which doesn't read the whole file, as those could be quite big, and still rely on the acTL before IDAT rule:

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

Speed is enhanced from 5x to 10x or more depending on how big the file is, and it also uses a lot less memory.

NB: this maybe could be tweaked more with the size given to fread or with the concatenation of the previous chunk with the current one. By the way, we need this concatenation as the acTL/IDAT word might be split between two read chunks.

早茶月光 2024-10-15 21:37:21

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

Here is my function that scans the chunk structure, not just substring inside the file (to prevent a false-positive if the acTL substring appears in the metadata instead of the chunk name).
For simplicity i used SplFileObject, the speed can be improved by using fopen/fread/fclose directly.

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;
}
难得心□动 2024-10-15 21:37:21

如果任何 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
}

If any JS coder stumbles in here - Javascript version of 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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文