Расположение Pixel-данных в памяти?
Я пишу библиотеку C++ для формата изображения, основанного на PNG. Одна остановка для меня заключается в том, что я не уверен, как мне следует размещать данные пикселей в памяти; Насколько я знаю, есть два практических подхода:
- Массив размера (ширина * высота); каждый пиксель может быть доступен с помощью массива [y*width + x].
- Массив размера (высоты), содержащий указатели на массивы размера (ширины).
Стандартная эталонная реализация для PNG (libpng) использует метод 2, описанный выше, в то время как я видел, как другие используют метод 1. Является ли один лучше другого, или каждый из них является методом со своими плюсами и минусами, где компромисс должен быть сделано? Кроме того, какой формат используют большинство систем графического отображения (возможно, для простоты использования вывода моей библиотеки в другие API)?
4 ответа
С верхней части моей головы:
- Единственное, что заставило бы меня выбрать #2, это то, что ваши требования к памяти немного смягчены. Если бы вы пошли на #1, система должна быть в состоянии выделить
height * width
количество непрерывной памяти. Принимая во внимание, что в случае № 2, он имеет свободу выделять меньшие порции непрерывной памяти размеромwidth
(может бытьheight
) из областей, которые являются свободными. (Когда вы учитываете каналы на пиксель, № 1 может не работать даже для изображений среднего размера.) - Кроме того, это может быть немного лучше при замене строк (или столбцов), если это требуется для целей манипулирования изображениями (достаточно замены указателя).
- Недостатком для # 2 является, конечно, дополнительный уровень косвенности, который проникает в каждый доступ и массив указателей, которые должны поддерживаться. Но это вряд ли вопрос сегодняшней скорости процессора и памяти.
- Вторым недостатком # 2 является то, что данные не обязательно соседствуют друг с другом, что затрудняет загрузку нужных страниц памяти в кэш процессором.
Преимущество метода 2 (разрезание массива по строкам) состоит в том, что вы можете выполнять операции с памятью поэтапно, например, изменять размер или перетасовывать изображение, не перераспределяя весь кусок памяти сразу. Для действительно больших изображений это может быть преимуществом.
Преимущество одного массива в том, что ваши вычисления проще, т. Е. Чтобы пройти одну строку вниз, вы делаете
pos += width;
вместо того, чтобы ссылаться на указатели. Для небольших и средних изображений это, вероятно, быстрее. Если вы не имеете дело с изображениями сотен Мб, я бы выбрал способ 1.
Я подозреваю, что libpng делает это (стиль 2) по нескольким возможным причинам:
- Избегайте больших выделений (как уже упоминалось) и могут упростить обработку ОЧЕНЬ больших PNG, особенно в системах без ВМ
- (возможно) позволяет чередовать декодировать аля чередующийся JPEG (если PNG поддерживает это)
- Легкость некоторых преобразований (вертикальное отражение) (маловероятно)
- Простота масштабирования (вставка или удаление строк без необходимости полного второго буфера, или расширение / узкие линии) (маловероятно, но возможно)
Проблема с этим подходом (предполагая, что каждая строка является выделением) является ОЧЕНЬ большим выделением / свободными издержками и, возможно, стимулирует фрагментацию памяти.
Если у вас нет веских причин, используйте стиль 1 (одиночное распределение) и, возможно, округлите до "хорошей" границы для используемой вами архитектуры (может быть 4, 8, 16 или, возможно, даже больше байтов). Обратите внимание, что многие библиотечные функции могут искать стиль 1 без отступов - подумайте, как вы будете использовать это и куда вы будете их передавать.
Сама Windows использует вариант метода 1. Каждая строка изображения дополняется кратным 4 байтам, и порядок цветов - B,G,R вместо более нормальных R,G,B. Также первая строка буфера является нижней строкой изображения.