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)