Развлекаясь на досуге с OpenGL, решил научиться делать скриншоты средствами программы, а не системы. Довольно быстро нагуглил функцию glReadPixels, но вот с сохранением картинки вышла проблема. Вспомнил былые времена, когда полностью своим кодом сохранял в bmp, нашел функцию сохранения в tga, понял, что все эти варианты попахивают велосипедизмом и решил использовать широко распространенную библиотеку. Выбор пал на libpng.
Дальше пошли грабли.
Оказалось, что никакого описания библиотеки на русском языке нет (собственно поэтому и пишу сейчас этот пост), а английская документация написана не самым удобным образом и не содержит даже простейшего полноценного примера использования.
Ниже я постараюсь объяснить, как средствами libpng сохранить изображение в простейшем случае.
Прежде всего надо подключить заголовочный файл
#include <png.h>
В функции/методе, в которой будем сохранять изображение, откроем файл, в который будем сохранять и создадим структуру png.
void
Renderer::screenshoot(const std::string& name) {
FILE *fp = fopen(name.c_str(), "wb");
if (!fp) {
return;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) {
goto close_file;
}
Теперь нужно создать структуру информации о png, вызвать setjmp, на случай ошибок и инициализировать вывод в файл.
png_infop png_info;
if (!(png_info = png_create_info_struct(png_ptr))) {
goto destroy_write;
}
if (setjmp(png_jmpbuf(png_ptr))) {
goto destroy_write;
}
png_init_io(png_ptr, fp);
Дальше нужно установить параметры изображения (размеры, количество цветов, количество битов на канал цвета, черезстрочность и сжатие. А также сформировать изображение и массив указателей на отдельные строки изображения.
Тут начинаются грабли.
Грабли №1: функция glReadPixels отдает изображение, строчки в котором идут снизу вверх, хотя обычно работая с графикой мы подразумеваем обратный порядок.
Грабли №2: если указать функции glReadPixels, что мы хотим получить цвета RGBA, то на самом деле мы получим их в порядке ARGB. Досадно, но на то, чтобы выяснить это, у меня ушел час, в течении которого я не понимал, почему у меня не правильно отображаются цвета на скриншоте.
Объявим массив data, в котором будут данные для libpng, и массив argb_data, в котором будут данные от OpenGL, ну и не забудем про массив указателей rows.
png_set_IHDR(png_ptr, png_info, width, height, 0, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
unsigned char data[width*height*3], argb_data[width*height*4];
unsigned char *rows[height];
render();
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, argb_data);
for (int i = 0; i < height; ++i) {
rows[height - i - 1] = data + (i*width*3);
for (int j = 0; j < width; ++j) {
int i1 = (i*width+j)*3;
int i2 = (i*width+j)*4;
data[i1++] = argb_data[++i2];
data[i1++] = argb_data[++i2];
data[i1++] = argb_data[++i2];
}
}
Теперь дело за малым — сохранить изображение, завершить ввод и обработать ошибки.
png_set_rows(png_ptr, png_info, rows);
png_write_png(png_ptr, png_info, PNG_TRANSFORM_IDENTITY, nullptr);
png_write_end(png_ptr, png_info);
destroy_write:
png_destroy_write_struct(&png_str, nullptr);
close_file:
fclose(fp);
}
Таким, относительно несложным образом можно сохранить изображение в png. Однако, не думайте, что это все, на что способна эта библиотека. Можно следить за процессом сохранения, применять фильтры, использовать черезстрочность, сжатие… Но чтобы разобраться с этим стоит читать оригинальную документацию.
UPD. Спасибо MrGobus за замечание по поводу glReadPixels и порядка строк.