Распараллеливание создания PNG-файлов с помощью C++, libpng и OpenMP

в настоящее время я пытаюсь реализовать png-кодер на C++ на основе libpng, который использует OpenMP для ускорения процесса сжатия. Инструмент уже способен генерировать PNG-файлы из различных форматов изображений. Я загрузил полный исходный код в pastebin.com так что вы можете видеть, что я сделал до сих пор:http://pastebin.com/8wiFzcgV

до сих пор, так хорошо! Теперь моя проблема заключается в том, чтобы найти способ распараллеливания генерации кусков IDAT, содержащих сжатые данные изображения. Обычно функция libpng png_write_row вызывается в цикле for с указателем на структуру, содержащую всю информацию о файле PNG и указателем строки с данными пикселя одной строки изображения.

(строка 114-117 в файле Pastebin)

//Loop through image
for (i = 0, rp = info_ptr->row_pointers; i < png_ptr->height; i++, rp++) {
    png_write_row(png_ptr, *rp);
}

Libpng затем сжимает одну строку за другой и заполняет внутренний буфер сжатыми данными. Как только буфер заполнен, сжатые данные сбрасываются в idat-фрагменте изображения файл.

мой подход состоял в том, чтобы разделить изображение на несколько частей и позволить одному потоку сжать строку 1 до 10, а другому потоку 11 до 20 и так далее. Но поскольку libpng использует внутренний буфер, это не так просто, как я думал сначала :) я каким-то образом должен заставить libpng записывать сжатые данные в отдельный буфер для каждого потока. После этого мне нужен способ объединить буферы в правильном порядке, чтобы я мог записать их все вместе в выходной файл изображения.

Итак, кто-то есть идея, как я могу сделать это с OpenMP и некоторыми настройками libpng? Большое спасибо!

2 ответов


Это слишком долго для комментария, но на самом деле это не ответ -

Я не уверен, что вы можете сделать это без изменения libpng (или написания собственного кодера). В любом случае, это поможет, если вы поймете, как реализуется сжатие PNG:

на высоком уровне изображение представляет собой набор строк пикселей (обычно 32-разрядные значения, представляющие кортежи RGBA).

каждая строка может независимо иметь фильтр приложенный к нему -- подошва фильтра цель состоит в том, чтобы сделать строку более "сжимаемой". Например, фильтр " sub " делает значение каждого пикселя разницей между ним и тем, который находится слева. На первый взгляд эта дельта-кодировка может показаться глупой, но если цвета между соседними пикселями похожи (что имеет тенденцию быть так), то результирующие значения очень малы независимо от фактических цветов, которые они представляют. Такие данные легче сжимать, потому что они гораздо более повторяющиеся.

спускаясь на уровень, изображение данные можно рассматривать как поток байтов (строки больше не отличаются друг от друга). Эти байты сжимаются, создавая другой поток байтов. Сжатые данные произвольно разбиваются на сегменты (где угодно!) записывается в один IDAT-Чан каждый (вместе с небольшими накладными расходами на каждый чан, включая контрольную сумму CRC).

самый низкий уровень приводит нас к интересной части,которая является шагом сжатия. Формат PNG использует zlib сжатый формат данных. сам zlib-это просто оболочка (с большей бухгалтерией, включая контрольную сумму Adler-32) вокруг реального сжатого формата данных, сдуется (zip-файлы тоже пользуюсь этим). deflate поддерживает два метода сжатия: кодирование Хаффмана (которое уменьшает количество битов, необходимых для представления некоторой байт-строки, до оптимального числа с учетом частоты, с которой каждый байт встречается в строке) и кодирование LZ77 (которое позволяет дублировать строки, которые уже произошли ссылаться вместо того, чтобы писать на вывод дважды).

хитрая часть о распараллеливании сжатия дефляции заключается в том, что в целом сжатие одной части входного потока требует, чтобы предыдущая часть также была доступна в случае, если на нее нужно ссылаться. но, так же, как PNGs может иметь несколько кусков IDAT, deflate разбивается на несколько "блоков". Данные в одном блоке могут ссылаться на ранее закодированные данные в другом блоке, но это не так есть to (конечно, это может повлиять на коэффициент сжатия, если это не так).

Итак, общей стратегией распараллеливания deflate было бы разбить вход на несколько большой разделы (чтобы степень сжатия оставалась высокой), сжимайте каждую секцию в ряд блоков, затем склейте блоки вместе (это на самом деле сложно, так как блоки не всегда заканчиваются на границе байта-но вы можете поместить пустой несжатый блок (тип 00), который будет выровнять по границе байта, между разделами). Однако это не тривиально и требует контроля над самым низким уровнем сжатия (создание блоков дефляции вручную), создание правильной оболочки zlib, охватывающей все блоки, и заполнение всего этого в куски IDAT.

Если вы хотите пойти со своей собственной реализацией, я бы предложил прочитать моя собственная реализация zlib / deflateкак я его использую), который я специально создал для сжатия PNGs (он написан на HaXe для Flash, но должен быть сравнительно легко портирован на C++). Поскольку Flash однопоточен, я не делаю никакого распараллеливания, но я разделяю кодировку на практически независимые разделы ("практически", потому что между разделами сохраняется дробно-байтовое состояние) на несколько кадров, что в значительной степени одно и то же.

удачи!


Я, наконец, получил его, чтобы распараллелить процесс сжатия. Как упоминалось Кэмероном в комментарии к его ответу, мне пришлось удалить заголовок zlib из zstreams, чтобы объединить их. Удаление нижнего колонтитула не требовалось, поскольку zlib предлагает опцию z_sync_flush, которая может использоваться для всех фрагментов (кроме последнего, который должен быть записан с помощью Z_FINISH) для записи на границу байта. Таким образом, вы можете просто объединить выходные данные потока после этого. В конце концов, контрольная сумма adler32 должна быть вычисляется по всем потокам и копируется в конец Объединенных zstreams.

Если вас интересует результат, вы можете найти полное доказательство концепции в https://github.com/anvio/png-parallel