Как стать автором
Обновить

Комментарии 16

Дааа, обмельчали статьи на Хабре.
Я не ставил цель описать полностью работу с libpng — для этого необходимо понимать все настройки изображения, которые можно выставить. Понимая их разобраться в libpng не сложно.

Цель моего поста — экономия времени тех, кому как и мне нужен минимум. Я потратил на это полтора часа, буду рад, если кто-то благодаря этому посту потратит всего 15 минут. Повторюсь, но толкового примера использования libpng я не встретил, тем более в рунете.
glReadPixels returns pixel data from the frame buffer, starting with the pixel whose lower left corner is at location (x, y),

Это не пнг хранит картинку вверх ногами а OpenGL возвращает ее такой, так как в нем используется такая система координат. Вообще, при работе с OpenGL, рекомендую отказаться от стереотипа, что картинка рисуется от верхнего левого угла и считать все от нижнего левого угла, это избавит вас от огромного числа граблей, например если ваши игры с OpenGL дойдут до уровня фреймбуфера и рендера в текстуру, то вы заметите, что изображение полученное при рендере в текстуру будет вверх ногами, но вам будет казаться что это грабли и баг, более того вы можете начать писать алгоритмы которые бы учитывая, что рендер в текстуру переворачивали бы изображение за счет ortho или еще как…
В общем както так.

Все таки знать английский стоит, и также стоит читать документацию которая идет вместе с библиотекой.
libpng-manual.txt содержит исчерпывающую информацию о библиотеке, решая проблемы с граблями порядка цветовых компанент.

PNG files store 3 color pixels in red, green, blue order. This code
would be used if they are supplied as blue, green, red:

png_set_bgr(png_ptr);

glReadPixel тоже позволяет решить эту проблему задавая формат GL_RGBA GLBGRA

format
Specifies the format of the pixel data. The following symbolic values are accepted: GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_RED, GL_GREEN, GL_BLUE, GL_RGB, GL_BGR, GL_RGBA, and GL_BGRA.

type
Specifies the data type of the pixel data. Must be one of GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE_3_3_2, GL_UNSIGNED_BYTE_2_3_3_REV, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_5_6_5_REV, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_4_4_4_4_REV, GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_1_5_5_5_REV, GL_UNSIGNED_INT_8_8_8_8, GL_UNSIGNED_INT_8_8_8_8_REV, GL_UNSIGNED_INT_10_10_10_2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_24_8, GL_UNSIGNED_INT_10F_11F_11F_REV, GL_UNSIGNED_INT_5_9_9_9_REV, or GL_FLOAT_32_UNSIGNED_INT_24_8_REV.

Ручная перестановка это както не комильфо. Ну и точно не грабли.
Спасибо. Сейчас подправлю пост.

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

С форматом и типом я ковырялся в течении часа, в пост вынес тот способ, который заработал.
Проблема не сколько в RGB vs. BGR (с этим всё в порядке), проблема в альфа канале, значение которого идет не после значений цветов, а перед ними. Если это не учитывать, то с картинкой творятся странные вещи.
А альтернативных способов обработки ошибок в libpng так и не появилось, так и делается все исключительно через setjmp?
Её можно собрать без поддержки setjmp, но альтернативы не предложено.
Что можете предложить для чистого Си, кроме как писать идеальный код и давать на вход идеальные данные для этого? Экзепшинов то нет.
Ну а как тысячи других C библиотек обходятся без setjmp? Как ядро линукса обходится без setjmp для обработки внутренних ошибок? Как стандартная C library обходится без них? Ответ один, — «случилось что то не так, — верни минус один» (ну или аналогичное значение) ( извиняюсь за рифму, случайно вышло). В нормальном С коде, каждый код возврата, в случае если функция может вернуть ошибку, должен провеняться вызывающей стороной, и если ошибка случилась, она должна быть обработана соответствующим образом, и в большинстве случаев — передана выше, как уже код возврата функции стоящей на одну позицию выше в стеке вызова, итп, итп. И все так и делают, за исключением редких «гениальных библиотек», вроде libpng или исходников lua.
Такой подход неправилен тем что:
1)Тип возвращаемого значения не всегда совместим с дескриптором ошибки
2)Обрабатывать исключения можно только на шаг выше колстека, а екзепшен можно кинуть куда угодно.

П.С. Отношение плотно работающих с «железом» программистов к исключениям варьируется от «лучше не надо», до «категорически против» — потому в ядре линукс их нет, в недрах епл кста тоже запрещено использовать исключения при работе с компонентами ОС — такие дела, почему не знаю.
Про п1 — приведите пример.

Про п2 — С — язык без исключений, и надо с этим либо жить, либо писать на C++ а не на C :) Такие вещи как setjmp порождают куда больше проблем, чем решают, по этому — это костыли. Если нужно «пробросить» ошибку через несколько уровней вызовов, то каждый из вложенных уровней, должен проверить на ошибку, и вернуть свою ошибку, и так всегда и делают.

Про Apple — по тому что в ядре очень ограниченный C++ Runtime, там много вещей из «обычного C++» нельзя делать, если вы про IOKit / NKE драйвера говорите.

P.S. Про проблемы с setjmp, — представьте такую ситуацию: есть библиотека «libA», написанная на C, содержащая функции X and Y, каждый из которых может «выкинуть» setjmp, а так-же плюс есть некий callback регистрируемый в этой библиотеке. И представьте, что пользуется этой C-библиотекой некая программа на C++ (частый случай). Вот она регистрирует callback, вот он в какой-то момент вызывается, внутри коллбэка C++ код вызывает другой метод библиотеки — скажем «X», там что-то идет не так, и библиотека дергает setjmp. Что имеем? Если внутри коллбэка были какие-то обьекты расположенные на стеке, то мы имеем кучу обьектов, для которых не был вызван деструктор, как следствие — утечки памяти, и прочие глюки C++ рантайма. В итоге, когда из C++ кода приходится (ввиду отсутствия альтернатив) общатся с подобной C библиотекой, приходится городить огромные костыли. (Пример подобного C кода — lua движек например).
п1. Да пожалуйста — математическая функция для которой -1 может быть результатом вычислений, функция возвращающая объект не ввиде указателя, функция имеющая тип void (внезапно) или возвращающая булевый тип — короче на практике зарезервировать -1 как дескриптор на все ошибки та еще задача.
Я сам, если честно, против setjmp — на моей практике ни один отладчик брошенное таким образом эрзац исключение корректно не обрабатывал, откуда оно было брошено и почему выяснить удавалось только ручной трассировкой и это печально.
> математическая функция

никто не обязывает использовать строго -1, код ошибки может быть совершенно любой. Если речь идет о функции возвращающей float/double, то у этих типов есть специальные значения для сигнализации о невычислимости функции для данных аргументов, — INF и NaN (бесконечность и «Not a Number»). Можно использовать их. Если функция возвращает int или другой целый тип, то у нее часто область значений ограничена все равно, так что можно в качестве кода ошибки использовать какой-нибудь INT_MAX или INT_MIN. Если ничто из этого не применимо, то можно сделать вот так:

int err = 0;
int result = function(arg1, arg2, ..., &err);
if ( err = -1 ) 
{
      // ошибка
}


> функция возвращающая объект не ввиде указателя

Классический паттерн, который все используют для такого случая:


object_t obj;

int err = createObject( arg1, ... argN,  &obj);

if ( err == -1 ) 
{ 
   ...


> функция имеющая тип void (внезапно)

Функция может зафейлится, но возвращает void? За такое молодым программистам по рукам указкой бьют.

> возвращающая булевый тип

Ну так возвращайте не булевый тип, а enum какой-нибудь трехстейтный, как это делают многие библиотеки.

> короче на практике зарезервировать -1 как дескриптор на все ошибки та еще задача.

Я не говорил что именно -1 надо использовать, — под каждый случай свой код ошибки, в зависимости от типа возвращаемого значения, и кучи других функторов, -1 — это метафорическое обобщение способа обработки ошибок принятого в С.
А зачем для скриншота RGBA? Можно просто RGB и тогда не будет проблем с сохранением альфа канала. На самом деле веселуха если изображение 8 битное и есть несколько зон в палитре. :)
Потому что если указать GL_RGB, то проблема остается — на каждый пиксель все равно идет четыре байта. Разницы нет.
Что у вас за видео драйвер? И система big-endian или little-endian?
www.opengl.org/sdk/docs/man/xhtml/glReadPixels.xml — что то я таких странностей в спецификации не нахожу.
Используйте GL_UNSIGNED_BYTE и будет вам счастье.
glReadPixels(0,0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, output);
По-моему, у вас в примере есть опечатка

destroy_write:
    png_destroy_write_struct(&png_str, nullptr);
close_file:
    fclose(fp);
}

В этом фрагменте удаляется png_str, но такой переменной не объявлялось
По-видимому, имелась в виду переменная png_ptr

Спасибо за пример, это — как раз то, что я искал (если, конечно, всё заработает) :-)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации