С помощью libpng я пытаюсь извлечь фрагменты текста из 44-мегабайтного PNG-изображения (и желательно проверить, что данные PNG не искажены (например, отсутствуют IEND
и т. д.)). Я мог бы сделать это с помощью png_read_png
и png_get_text
, но для меня это заняло слишком много времени, 0,47 секунды, что, я почти уверен, из-за огромного количества фрагментов IDAT
в изображении. Как это сделать побыстрее?
Мне не нужны были пиксели, поэтому я попытался заставить libpng игнорировать фрагменты IDAT
.
IDAT
куски, я пробовал:png_read_info(p_png, p_png_information); png_read_image(p_png, nullptr); png_read_end(p_png, p_png_information);
пропускать IDAT
чанки; разбился и провалился.png_set_keep_unknown_chunks
, чтобы libpng не знал о IDAT
, и png_set_read_user_chunk_fn(p_png, nullptr, discard_an_unknown_chunk)
(discard_an_unknown_chunk
— это функция, которая делает return 1;
), чтобы отбрасывать неизвестные фрагменты; странная ошибка CRC произошла в первом фрагменте IDAT
и завершилась неудачно.Так и не удалось.
Работает как надстройка Node.js C++, в основном написанная на C++, в Windows 10, с процессором i9-9900K с тактовой частотой 3,6 ГГц и гигабайтами памяти.
Прочитайте файл изображения на SSD с помощью fs.readFileSync
, метода Node.js, возвращающего Buffer
, и передайте его в libpng для обработки.
Да, сначала я ругал libpng за долгие вычисления. Теперь я вижу, что могут быть и другие причины, вызывающие задержку. (Если это так, то этот вопрос будет плохим с проблемой XY.) Спасибо за ваши комментарии. Я проверю свой код еще раз более тщательно.
Поскольку каждый шаг для подачи входных данных PNG в надстройку C++ оставался неизменным, я в конечном итоге вручную выбирал и декодировал только текстовые фрагменты, используя магию указателя C и немного магии C++. И производительность была впечатляющей (0,0020829 секунд при обработке), причем почти мгновенно. Хотя не знаю почему и как.
B:\__A2MSUB\image-processing-utility>npm run test
> [email protected] test B:\__A2MSUB\image-processing-utility
> node tests/test.js
----- “read_png_text_chunks (manual decoding, not using libpng.)” -----
[
{
type: 'tEXt',
keyword: 'date:create',
language_tag: null,
translated_keyword: null,
content: '2020-12-13T22:01:22+09:00',
the_content_is_compressed: false
},
{
type: 'tEXt',
keyword: 'date:modify',
language_tag: null,
translated_keyword: null,
content: '2020-12-13T21:53:58+09:00',
the_content_is_compressed: false
}
]
----- “read_png_text_chunks (manual decoding, not using libpng.)” took 0.013713 seconds.
B:\__A2MSUB\image-processing-utility>
Может быть, вы можете поделиться PNG через Dropbox, Google Drive или что-то подобное? Что вы подразумеваете под «предпочтительно проверить данные PNG»? Подтвердить что? Каковы размеры в пикселях и битовая глубина / тип цвета этого массивного файла, потому что а) ваш диск должен читать 44 МБ, что может занять 0,3 с на диске со скоростью 150 МБ / с, и б) вы можете выделить ГБ памяти для расширения сжатого PNG в.
Вы также можете уточнить свою среду — на Raspberry Pi Zero с USB 2.0 Memory Stick 0,47 с будет впечатляющим.
Вы можете проверить, что все правильные фрагменты PNG находятся в файле, в правильном порядке, не повторяются и с правильными контрольными суммами, используя pngcheck. Это с открытым исходным кодом, так что вы можете посмотреть, как это работает.
Если вы добавите параметр -7
, то сможете не только проверить структуру, но и извлечь текст:
pngcheck -7 a.png
Выход
File: a.png (60041572 bytes)
date:create:
2020-12-24T13:22:41+00:00
date:modify:
2020-12-24T13:22:41+00:00
OK: a.png (10000x1000, 48-bit RGB, non-interlaced, -0.1%).
Я сгенерировал PNG-файл размером 60 МБ, и приведенная выше проверка занимает 0,067 с на моем MacBook Pro.
Мне пришлось сделать что-то подобное, но я хотел, чтобы libpng выполнял анализ всех фрагментов метаданных (например, фрагментов eXIf
, gAMA
, pHYs
, zEXt
, cHRM
и т. д.). Некоторые из этих фрагментов могут появляться после IDAT
, что означает, что метаданные нельзя прочитать только с помощью png_read_info
. (Единственный способ добраться до них — сделать полную декодировку изображения, что стоит дорого, а затем вызвать png_read_end
.)
Мое решение состояло в том, чтобы создать синтетический поток байтов PNG, который передается в libpng через набор обратного вызова чтения с использованием png_set_read_fn
. В этом обратном вызове я пропускаю все фрагменты IDAT
в исходном PNG-файле, и когда я добираюсь до фрагмента IEND
, я вместо этого испускаю фрагмент IDAT
нулевой длины.
Теперь я вызываю png_read_info
: он анализирует все метаданные во всех фрагментах, которые видит, останавливаясь на первом IDAT
, который в моем синтетическом потоке PNG на самом деле является концом исходного PNG-изображения. Теперь у меня есть все метаданные, и я могу запросить их у libpng через функции png_get_xxx
.
Обратный вызов чтения, который создает синтетический поток PNG, немного сложен из-за того, что он вызывается libpng несколько раз, каждый раз для небольших участков потока. Я решил это с помощью простого конечного автомата, который постепенно обрабатывает исходный PNG, создавая синтетический поток PNG на лету. Вы можете избежать этих сложностей, если создадите синтетический поток PNG заранее в памяти перед вызовом png_read_info
: без каких-либо реальных IDAT
ваш полный синтетический поток PNG обязательно будет маленьким...
Хотя у меня нет бенчмарков, чтобы поделиться здесь, окончательное решение получается быстро, потому что IDAT
полностью пропускаются и не декодируются. (Я использую поиск файла, чтобы пропустить каждый IDAT
в исходном PNG после чтения 32-битной длины фрагмента.)
Какой хитрый подход только с libpng!
Почему бы вам не запустить его под профилировщиком и посмотреть, куда тратится время, вместо того, чтобы гадать? Libpng имеет открытый исходный код, а Vtune в настоящее время бесплатен.