我已将 PNG(以及 JPEG)图像上传到我的网站。
它们应该是静态的(即一帧)。
有APNG之类的东西。
(它将在 Firefox 中显示动画)。
根据维基百科文章...
APNG 将后续帧隐藏在 PNG 辅助块中,这样不支持 APNG 的应用程序就会忽略它们,但格式不会发生任何更改,以允许软件区分动画和非动画图像。
这是否意味着无法通过代码确定 PNG 是否为动画?
如果可能的话,您能给我指出正确的 PHP 方向吗(GD、ImageMagick)?
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;
}
?>
AFAIK,不支持 APNG 的库将只获取 PNG 的第一帧。对于您的情况,您可以从 APNG(或 PNG、JPEG 等)创建一个新图像,然后将其重新保存为 PNG。如果使用 GD,它应该剥离动画数据,除非库已更新为支持 APNG。
我想建议一个更优化的版本,它不会读取整个文件,因为这些文件可能很大,并且仍然依赖 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 字可能会被分割在两个读取块之间。
如果任何 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
}
这是我的函数,它扫描块结构,而不仅仅是文件内的子字符串(如果
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;
}