Сохранение изображения с помощью libpng

в 5:46, , рубрики: c++, OpenGL, обработка изображений, метки:

Сохранение изображения с помощью libpng
Развлекаясь на досуге с 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, связанные с libpng: библиотека считает, что строчки идут снизу вверх, хотя обычно работая с графикой мы подразумеваем обратный порядок.
Грабли №2, связанные с OpenGL: если указать функции 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. Однако, не думайте, что это все, на что способна эта библиотека. Можно следить за процессом сохранения, применять фильтры, использовать черезстрочность, сжатие… Но чтобы разобраться с этим стоит читать оригинальную документацию.

Автор: alexac

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js