我将 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
)。
这段代码(取自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;
}
?>
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 字可能会在两个读取块之间拆分。
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;
}