Могу ли я программно определить, является ли PNG анимированным?
У меня есть PNG (а также JPEG) изображения, загруженные на мой сайт.
Они должны быть статичными (то есть один кадр).
Существует такая вещь, как APNG.
(это будет анимировано в Firefox).
Согласно статье в Википедии...
APNG скрывает последующие кадры во вспомогательных фрагментах PNG таким образом, что приложения, не поддерживающие APNG, будут их игнорировать, но в остальном нет никаких изменений в формате, позволяющих программам различать анимированные и неанимированные изображения.
Означает ли это, что невозможно определить, анимируется ли PNG с кодом?
Если это возможно, не могли бы вы указать мне правильное направление в отношении PHP (GD, ImageMagick)?
5 ответов
Изображения APNG спроектированы так, что они "маскируются" под PNG для читателей, которые их не поддерживают. То есть, если читатель не поддерживает их, он просто предположит, что это обычный файл PNG и отобразит только первый кадр. Это означает, что они имеют тот же тип MIME, что и PNG (image/png), у них одинаковое магическое число (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.
Я хотел бы предложить более оптимизированную версию, которая не читает весь файл, так как они могут быть довольно большими и при этом полагаться на acTL перед правилом IDAT:
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;
}
Скорость увеличивается с 5x до 10x и более в зависимости от размера файла, а также использует намного меньше памяти.
NB: это может быть изменено больше с размером, заданным для fread, или с конкатенацией предыдущего фрагмента с текущим. Кстати, нам нужна эта конкатенация, поскольку слово acTL/IDAT может быть разделено на два блока чтения.
Вот моя функция, которая сканирует структуру фрагментов, а не только подстроку внутри файла (чтобы предотвратить ложное срабатывание, если
acTL
подстрока появляется в метаданных вместо имени блока). Для простоты я использовал SplFileObject, скорость можно улучшить, используя fopen / fread / fclose напрямую и читая / анализируя 8 байтов за раз.
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(4);
if (strlen($bytes) < 4) {
return false;
}
$length = unpack('N', $bytes)[1];
$chunkname = $f->fread(4);
switch ($chunkname) {
case 'acTL':
return true;
case 'IDAT':
return false;
}
$f->fseek($length + 4, SEEK_CUR);
}
return false;
}
Если какой-либо кодировщик JS наткнется здесь - версия Javascript /questions/10794131/mogu-li-ya-programmno-opredelit-yavlyaetsya-li-png-animirovannyim/10794136#10794136
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
}