How to take screenshot in OpenGL
Asked Answered
W

8

21

How to take a screenshot of an OpenGL window in C++ and save it to file.

I found the glReadPixels() function, but I don't know what to do next. Where I can set path to a file, for example?

If not difficult, write code, please.

Wexford answered 30/4, 2011 at 21:53 Comment(0)
G
30

This piece of code captures the OpenGL window and export to a BMP file. You must have FreeImage library to run it.

// Make the BYTE array, factor of 3 because it's RBG.
BYTE* pixels = new BYTE[3 * width * height];

glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);

// Convert to FreeImage format & save to file
FIBITMAP* image = FreeImage_ConvertFromRawBits(pixels, width, height, 3 * width, 24, 0x0000FF, 0xFF0000, 0x00FF00, false);
FreeImage_Save(FIF_BMP, image, "C:/test.bmp", 0);

// Free resources
FreeImage_Unload(image);
delete [] pixels;
Gunrunning answered 4/8, 2011 at 8:56 Comment(3)
Would be sensible to use GL_UNSIGNED_BYTE for the array too, instead of the windows-specific BYTE.Cluny
Sorry, I meant GLubyte.Cluny
@Mortennobel Would be better if you used GL_PACK_ALIGNMENT, since glReadPixels is packing and not unpacking :)Ultramundane
C
13

glReadPixels will copy the bits into a memory buffer that you supply. You have to manually format the data (to the image format of your choice) and write it to disk after glReadPixels returns.

Credent answered 30/4, 2011 at 22:1 Comment(2)
With what function may write memory buffer to disk?Wexford
@EthanHunt: fwrite writes data to a FILE*, fopen gives you a FILE* corresponding to a filename you specify. But don't ignore the part about the image format, that's the tricky bit.Memberg
B
8

Runnable example

Each time you click with the mouse on the window, a tmpX.ppm file is created with the current screenshot.

You can view this file for example with eog on Linux, and inspect it with a text editor.

To render without showing a window, see: How to use GLUT/OpenGL to render to a file?

#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#define GL_GLEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/glext.h>

static GLubyte *pixels = NULL;
static const GLenum FORMAT = GL_RGBA;
static const GLuint FORMAT_NBYTES = 4;
static const unsigned int HEIGHT = 500;
static const unsigned int WIDTH = 500;
static unsigned int nscreenshots = 0;
static unsigned int time;

/* Model. */
static double angle = 0;
static double angle_speed = 45;

static void init(void)  {
    glReadBuffer(GL_BACK);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glViewport(0, 0, WIDTH, HEIGHT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);

    pixels = malloc(FORMAT_NBYTES * WIDTH * HEIGHT);
    time = glutGet(GLUT_ELAPSED_TIME);
}

static void deinit(void)  {
    free(pixels);
}

static void create_ppm(char *prefix, int frame_id, unsigned int width, unsigned int height,
        unsigned int color_max, unsigned int pixel_nbytes, GLubyte *pixels) {
    size_t i, j, k, cur;
    enum Constants { max_filename = 256 };
    char filename[max_filename];
    snprintf(filename, max_filename, "%s%d.ppm", prefix, frame_id);
    FILE *f = fopen(filename, "w");
    fprintf(f, "P3\n%d %d\n%d\n", width, HEIGHT, 255);
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            cur = pixel_nbytes * ((height - i - 1) * width + j);
            fprintf(f, "%3d %3d %3d ", pixels[cur], pixels[cur + 1], pixels[cur + 2]);
        }
        fprintf(f, "\n");
    }
    fclose(f);
}

static void draw_scene() {
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    glRotatef(angle, 0.0f, 0.0f, -1.0f);
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f( 0.0f,  0.5f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-0.5f, -0.5f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f( 0.5f, -0.5f, 0.0f);
    glEnd();
}

static void display(void) {
    draw_scene();
    glutSwapBuffers();
    glReadPixels(0, 0, WIDTH, HEIGHT, FORMAT, GL_UNSIGNED_BYTE, pixels);
}

static void idle(void) {
    int new_time = glutGet(GLUT_ELAPSED_TIME);
    angle += angle_speed * (new_time - time) / 1000.0;
    angle = fmod(angle, 360.0);
    time = new_time;
    glutPostRedisplay();
}

void mouse(int button, int state, int x, int y) {
    if (state == GLUT_DOWN) {
        puts("screenshot");
        create_ppm("tmp", nscreenshots, WIDTH, HEIGHT, 255, FORMAT_NBYTES, pixels);
        nscreenshots++;
    }
}

int main(int argc, char **argv) {
    GLint glut_display;
    glutInit(&argc, argv);
    glutInitWindowSize(WIDTH, HEIGHT);
    glutInitWindowPosition(100, 100);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    glutMouseFunc(mouse);
    atexit(deinit);
    glutMainLoop();
    return EXIT_SUCCESS;
}

Compile with:

gcc main.c -lm -lGL -lGLU -lglut

Tested on Ubuntu 15.10, OpenGL 4.5.0 NVIDIA 352.63.

Vulkan

This example just worked: https://github.com/SaschaWillems/Vulkan/blob/b9f0ac91d2adccc3055a904d3a8f6553b10ff6cd/examples/screenshot/screenshot.cpp how to run it: Is it possible to do offscreen rendering without Surface in Vulkan?

enter image description here

Boyse answered 26/3, 2016 at 15:3 Comment(0)
B
5

A simple and quick solution.

  • Outputs a TARGA file but can easily be converted to PNG (script provided).
  • No extra libraries required.
  • Will work with both C and C++ (with some minor changes).
  • Note: The output file should have the tga file extension.

Here is the code:

void saveScreenshotToFile(std::string filename, int windowWidth, int windowHeight) {    
    const int numberOfPixels = windowWidth * windowHeight * 3;
    unsigned char pixels[numberOfPixels];

    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, windowWidth, windowHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);

    FILE *outputFile = fopen(filename.c_str(), "w");
    short header[] = {0, 2, 0, 0, 0, 0, (short) windowWidth, (short) windowHeight, 24};

    fwrite(&header, sizeof(header), 1, outputFile);
    fwrite(pixels, numberOfPixels, 1, outputFile);
    fclose(outputFile);

    printf("Finish writing to file.\n");
}

And calling the function:

saveScreenshotToFile("test.tga", 1200, 900);

A bash script to convert TARGA files to PNG:

for oldFileName in *.tga; do
    [ -f "$oldFileName" ] || break         # Break out if no .tga files found.
    newFileName=${oldFileName//.tga/.png}
    convert $oldFileName $newFileName
    rm $oldFileName
    echo "Converted $oldFileName to $newFileName"
done
Backgammon answered 1/11, 2018 at 23:48 Comment(0)
L
4

Saving that data to a file is something you'll either have to do yourself or use a third-party library for - OpenGL has no such feature.

Windows .bmp is probably the easiest if you're looking to do it yourself - Wikipedia has a pretty good explanation of the file format. Otherwise you can use image saving/loading libraries: libpng, libjpeg, etc. for low-level control, or devIL (there are others, but this is my favorite, and it's an extremely versatile library that goes well with GL) for high-level "just do it" image i/o.

Loseff answered 30/4, 2011 at 22:2 Comment(0)
F
1

You can save screenshot with @Rafael's answer and OpenCV:

void Game::saveScreenshotToFile(std::string filename, int windowWidth, int windowHeight) {    

    cv::Mat img(windowHeight, windowWidth, CV_8UC3);
    glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);

    glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
    glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);
    cv::flip(img, img, 0);
    //cv::imshow("Image",img);
    //cv::waitKey(0);
    cv::imwrite(filename, img);
    }

Thanks for OpenCV: https://mcmap.net/q/17568/-converting-data-from-glreadpixels-to-opencv-mat

Feininger answered 15/9, 2020 at 15:36 Comment(0)
G
0

Generally, OpenGL don't provide functions to save image. I think the fastest and simplest way to do this is save to .PPM format. However, this kind of format is uncompressed which means it's file size would be very large. And it can be support only by quite a few programs nowadays.

I prefer to save image to .png file which is compressed but also gives lossless image and supported by many browsers. To save the OpenGL to .png format, I first recommend the PNGwriter. It's pretty simple and easy to use. For example, to save a pixel of a image with color (R, G, B) in the position (x, y), your code will be(see "quickstart" in the PNGwriter website):

pngwriter PNG(width, height, 1.0, fileName); // "1.0" stand for the white background
PNG.plot(x, y, R, G, B);
PNG.close();

Note that, since the PNGwriter save each pixel starting from the top-left corner of the image, while the array get from glReadPixels() start from the bottom-left of the window, your code to save the whole image might probably look like this:

GLfloat* pixels = new GLfloat[nPixels];
glReadPixels(0.0, 0.0, width, height,GL_RGB, GL_FLOAT, pixels);
pngwriter PNG(width, height, 1.0, fileName);
size_t x = 1;   
size_t y = 1;
double R, G, B;
for(size_t i=0; i<npixels; i++) // "i" is the index for array "pixels"
{
      switch(i%3)
     {
           case 2:
                 B = static_cast<double>(pixels[i]); break;
           case 1:
                 G = static_cast<double>(pixels[i]); break;
           case 0:
                 R = static_cast<double>(pixels[i]);
                 PNG.plot(x, y, R, G, B);     // set pixel to position (x, y)
                 if( x == width )             // Move to the next row of image
                 {
                       x=1;
                       y++;
                  }
                  else                       // To the next pixel
                  { x++; }
                  break;
     }
}
PNG.close();
Gymnastic answered 23/7, 2014 at 14:37 Comment(1)
See also: stackoverflow.com/questions/3191978/… for a PPM | PNG | FFMPEG one.Boyse
T
0

I have modified Ciro Santilli OurBigBook.com 's answer to include threading and better performance.

#include <thread>
#include <Windows.h>
#define GL_GLEXT_PROTOTYPES 1
#include <GL\GL.h>
#include <GL\GLU.h>
#include <glut.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>


static GLubyte *pixels = NULL;
static const GLenum FORMAT = GL_RGBA;
static const GLuint FORMAT_NBYTES = 4;
static const unsigned int HEIGHT = 500;
static const unsigned int WIDTH = 500;
static unsigned int nscreenshots = 0;
static unsigned int gameTime;
static bool grabScreen = false;
static bool threadRunning = false;


/* Model. */
static double angle = 0;
static double angle_speed = 45;

static void init(void)  {
    glReadBuffer(GL_BACK);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glViewport(0, 0, WIDTH, HEIGHT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);

    pixels = (GLubyte*)malloc(FORMAT_NBYTES * WIDTH * HEIGHT);
    gameTime = glutGet(GLUT_ELAPSED_TIME);
}

static void deinit(void)  {
    free(pixels);
}

static void create_ppm(char *prefix, int frame_id, unsigned int width, unsigned int height,
    unsigned int color_max, unsigned int pixel_nbytes, GLubyte *pixels) {
    size_t i, j, k, cur;
    enum Constants { max_filename = 256 };
    char filename[max_filename];
    sprintf(filename, "%s%d.ppm", prefix, frame_id);
    FILE *f = fopen(filename, "w");
    fprintf(f, "P3\n%d %d\n%d\n", width, HEIGHT, 255);
    for (i = 0; i < height; i++) {
        for (j = 0; j < width; j++) {
            cur = pixel_nbytes * ((height - i - 1) * width + j);
            fprintf(f, "%3d %3d %3d ", pixels[cur], pixels[cur + 1], pixels[cur + 2]);
        }
        fprintf(f, "\n");
    }
    fclose(f);
}

static void draw_scene() {
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    glRotatef(angle, 0.0f, 0.0f, -1.0f);
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.5f, 0.0f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-0.5f, -0.5f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.5f, -0.5f, 0.0f);
    glEnd();
}

static void savepixels()
{
    threadRunning = true;
    create_ppm("tmp", nscreenshots, WIDTH, HEIGHT, 255, FORMAT_NBYTES, pixels);
    nscreenshots++;
    threadRunning = false;
}

static void display(void) {
    draw_scene();
    glutSwapBuffers();
    if (grabScreen){
        grabScreen = false;
        if (threadRunning){
            printf("Thread running\n");
        }
        else{
            glReadPixels(0, 0, WIDTH, HEIGHT, FORMAT, GL_UNSIGNED_BYTE, pixels);
            puts("screenshot");
            std::thread t{ savepixels };
            t.detach();
        }
    }
}

static void idle(void) {
    int new_gameTime = glutGet(GLUT_ELAPSED_TIME);
    angle += angle_speed * (new_gameTime - gameTime) / 1000.0;
    angle = fmod(angle, 360.0);
    gameTime = new_gameTime;
    glutPostRedisplay();
}

void mouse(int button, int state, int x, int y) {
    if (state == GLUT_DOWN && grabScreen == false) {
        grabScreen = true;
    }
}

int main(int argc, char **argv) {
    GLint glut_display;
    glutInit(&argc, argv);
    glutInitWindowSize(WIDTH, HEIGHT);
    glutInitWindowPosition(100, 100);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow(argv[0]);
    init();
    glutDisplayFunc(display);
    glutIdleFunc(idle);
    glutMouseFunc(mouse);
    atexit(deinit);
    glutMainLoop();
    return EXIT_SUCCESS;
}
Tait answered 28/12, 2023 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.