Capturing a "screenshot" of the currently bound framebuffer (readback), can be done using glReadPixels. The following snippet uses stb_image_write.h to save the screenshot to a file.

Complete example can be found in the GLCollection repository on GitHub.

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

int saveScreenshot(const char *filename)
{
    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

    int x = viewport[0];
    int y = viewport[1];
    int width = viewport[2];
    int height = viewport[3];

    char *data = (char*) malloc((size_t) (width * height * 3)); // 3 components (R, G, B)

    if (!data)
        return 0;

    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);

    int saved = stbi_write_png(filename, width, height, 3, data, 0);

    free(data);

    return saved;
}

On success saveScreenshot returns 1 otherwise 0 on failure.

Top Left vs Bottom Left

In OpenGL the origin is at the bottom left corner. Most image formats and working with images in general, the origin is usually at the top left corner. As such it is likely needed to flip the image vertically.

If stb_image_write.h is being used, then it is possible to specify a flag to automatically flip the image when its being written to a file:

stbi_flip_vertically_on_write(1);

It's only necessary to call the above function once, at the start of program.

Flip Image Vertically

The image can be flipped vertically manually using the following code:

#include <string.h>

void flipVertically(int width, int height, char *data)
{
    char rgb[3];

    for (int y = 0; y < height / 2; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            int top = (x + y * width) * 3;
            int bottom = (x + (height - y - 1) * width) * 3;

            memcpy(rgb, data + top, sizeof(rgb));
            memcpy(data + top, data + bottom, sizeof(rgb));
            memcpy(data + bottom, rgb, sizeof(rgb));
        }
    }
}

Note the above function assumes the image data has 3 components (R, G, B).

The flipVertically function would be called in saveScreenshot after glReadPixels and prior to saving.

Timestamped Filename

If the application features a capture screenshot button, then it might be useful to timestamp the screenshots.

The following createScreenshotBasename function, creates a filename in the format of: year, month, day, "_", hour, minute, second, ".png".

Example: "20130902_143250.png".

#include <time.h>

const char* createScreenshotBasename()
{
    static char basename[30];

    time_t t = time(NULL);
    strftime(basename, 30, "%Y%m%d_%H%M%S.png", localtime(&t));

    return basename;
}

Making a complete captureScreenshot function, that captures a screenshot and saves it to a file in a "screenshots/" directory, can be done by combining saveScreenshot and createScreenshotBasename.

Bind the captureScreenshot function to a key press, and a screenshot will be saved with a timestamped filename, every time the button is pressed.

#include <stdio.h>

int captureScreenshot()
{
    char filename[50];

    strcpy(filename, "screenshots/");
    strcat(filename, createScreenshotBasename());

    int saved = saveScreenshot(filename);

    if (saved)
        printf("Successfully Saved Image: %s\n", filename);
    else
        fprintf(stderr, "Failed Saving Image: %s\n", filename);

    return saved;
}

Note that the above code assumes that the "screenshots/" directory exists.

Optimization

If screenshots are frequently captured, then consider only calling glViewport once. Alternatively use glfwGetFramebufferSize (if GLFW is used), or watch for resize events and store the size.

Why GL_PACK_ALIGNMENT?

Something usually forgotten is setting GL_PACK_ALIGNMENT, which specifies the alignment of each row (limited to 1, 2, 4, 8). The default value is 4, which isn't a problem if there's 4 components (R, G, B, A). However saveScreenshot expects and allocates for 3 components (R, G, B), which would result in padding being added in certain cases. To avoid this GL_PACK_ALIGNMENT must be set to 1 (tightly packed with no padding).

glPixelStorei(GL_PACK_ALIGNMENT, 1);

GL_PACK_ALIGNMENT vs GL_UNPACK_ALIGNMENT?

Something also confused around the Internet is the difference between GL_PACK_ALIGNMENT and GL_UNPACK_ALIGNMENT. When that is the case, the best solution is to look in the OpenGL Specification.

The OpenGL Specification (3.3 Core) specifies that GL_PACK_ALIGNMENT pertains to operations that read data from OpenGL (glReadPixels, glGetTexImage, etc). Whereas GL_UNPACK_ALIGNMENT pertains to operations that write data to OpenGL (glTexImage2D, etc).

The OpenGL Specification (3.3 Core) specifies on page 122 and 220 that:

  • GL_PACK_ALIGNMENT affects operations that read data from OpenGL (glReadPixels, glGetTexImage, etc)
  • GL_UNPACK_ALIGNMENT affects operations that write data to OpenGL (glTexImage2D, etc)