OpenGL Screenies, How To?

Started by
9 comments, last by shadowwz 19 years, 3 months ago
Ok, this next bit is going to sound strange, so bare with me please. I am trying to capture every frame of a OpenGL program in jpg/gif/tga format (pref jpg/gif). I am going to be using these to shrink down, and import into a flash website, FRAME BY FRAME... to horribly and painstakingly create the opengl animation in flash. What i do not know how to do, is any commands to capture a frame and print it in a specific format. My texture image data is all TGA, so i can live with that, however i REALLY need to make OpenGL save each frame. So, can anyone tell me how to do this? Links are much appreciated, explanations with those are awesome. I am a total nub so please spell it out as if i were 5, and if you know of any better ways please enlighten me. I think flash can take movie formats, but i am just concerned with getting it done in a manner i know how to, i dont know how well flash likes movie formats and how the quality will be. Thanks to any replies!
Rank: OpenGL & Glut "Nub", C++ Freshman.
Advertisement
I'm using SDL for setup stuff, and they have a convenient BMP save function, so using that:

void Write_BMP (char *FName){    char *Pixels;    SDL_Surface *Temp;    Uint32 rmask, gmask, bmask, amask;    int w, h;    int i, j, Top, Bottom;    char Swap;        w = SDL_GetVideoSurface ()->w;    h = SDL_GetVideoSurface ()->h;    Pixels = new char [4*w*h];    if (SDL_BYTEORDER == SDL_BIG_ENDIAN)    {        rmask = 0xff000000;        gmask = 0x00ff0000;        bmask = 0x0000ff00;        amask = 0x000000ff;    } else {        rmask = 0x000000ff;        gmask = 0x0000ff00;        bmask = 0x00ff0000;        amask = 0xff000000;    }        //  Get the data from OpenGL    glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, Pixels);        //  Flip it vertically    for (j = (h >> 1) - 1; j >= 0; --j)    {        Top = j * w * 4;        Bottom = (h - j - 1) * w * 4;        for (i = (w << 2) - 1; i>= 0; --i)        {            Swap = Pixels [Top + i];            Pixels [Top + i] = Pixels [Bottom + i];            Pixels [Bottom + i] = Swap;        }    }        //  Copy it into a SDL surface    Temp = SDL_CreateRGBSurfaceFrom (Pixels, w, h, 32, 4 * w, rmask, gmask, bmask, amask);        //  And save it    SDL_SaveBMP (Temp, FName);        //  And free the memory    delete [] Pixels;    SDL_FreeSurface (Temp);}


You'd need to include some other libs if you wanted JPEG.
hope it helps.
If you really need the screenshot in Targa format, here's some code to do it:

#include <windows.h>#include <gl/gl.h>#include <stdio.h>struct CTGAHeader_ForSaving{  char IDCount;  char ColorMap;  char ImageCode;  unsigned short CMOrigin;  char CMLength;  char CMBitDepth;  unsigned short IMXOrigin;  unsigned short IMYOrigin;  unsigned short IMWidth;  unsigned short IMHeight;  char IMBitDepth;  char IMDescByte;};/*  Takes a screenshot of the current opengl buffer.  Be sure to include the  extension in the filename, i.e. 'shot.tga' NOT 'shot'.*/void TakeScreenshot(const char* filename){  /* grab OpenGL buffer */    int VPort[4],FSize,PackStore;  unsigned char *PStore;  // get viewport dims (x,y,w,h)  glGetIntegerv(GL_VIEWPORT,VPort);    // allocate space for framebuffer in rgb format  FSize = VPort[2]*VPort[3]*3;  PStore = new unsigned char[FSize];  // store unpack settings  glGetIntegerv(GL_PACK_ALIGNMENT, &PackStore);  // setup unpack settings  glPixelStorei(GL_PACK_ALIGNMENT, 1);  // this actually gets the buffer pixels  glReadPixels(VPort[0],VPort[1],VPort[2],VPort[3],GL_RGB,GL_UNSIGNED_BYTE,PStore);  // restore unpack settings  glPixelStorei(GL_PACK_ALIGNMENT, PackStore);          /* dump data to disk */     CTGAHeader_ForSaving Header;    int loop;  FILE *pFile;  unsigned char SwapByte;    // setup header  Header.IDCount    = 0;  Header.ColorMap   = 0;  Header.ImageCode  = 2;  // uncompressed rgb image  Header.CMOrigin   = 0;  Header.CMLength   = 0;  Header.CMBitDepth = 0;  Header.IMXOrigin  = 0;  Header.IMYOrigin  = 0;  Header.IMWidth    = VPort[2];  Header.IMHeight   = VPort[3];  Header.IMBitDepth = 24; // rgb with no alpha  Header.IMDescByte = 0;    // swap red and blue  for (loop=0;loop<FSize;loop+=3)  {    SwapByte       = PStore[loop];    PStore[loop]   = PStore[loop+2];    PStore[loop+2] = SwapByte;      }    // open file  pFile = fopen( filename,"wb" );  if (pFile==NULL)  {    delete[] PStore;    return;  }         // write to disk  fwrite( &Header,sizeof(CTGAHeader_ForSaving),1,pFile ); // header  fwrite( PStore,FSize,1,pFile ); // image data    // close file  fclose( pFile );    // free memory  delete[] PStore;}


If you want to convert the files in a batch conversion I recommend using Irfanview.

Hope that helped.
--
Cheers,
Darren Clark
The glReadPixels function reads the pixels on the screen. This data can be exported by the above means, but a problem you will run into is speed. Writing to the hard drive once per frame takes CPU cycles. Play a game and save a screenshot to see the delay of the FPS. You could use glReadPixels and save the info into buffers, then save them file by file, but that depends on how much memory you have to do so. If you are limited by memory, you will have to save the buffers every so often, but writing to the hard drive isn't so bad if you do it all at once. The writing isn't so bad quantity wise. Writing 20 files and writing 1 file take close to the same amount of time, but only if you do the 20 files all at once. Doing it frame by frame will make the program/game unplayable. There are programs that read the screen buffer into videos, but they do basicaly the same thing with buffers.


hey, this is a dumb c++ questions, but i am not that good with data formats yet in c++ either.

How can i combine the string structure of a name "screenie" with an integer variable, so i can do it like "screenie0", screenie1, screenie2, ect..

And speed isnt a problem. It couldbe as slow as 1 frame per 10seconds, all i need to do is capture a full rotation of my movie(it loops obviously) and import it to flash.

I am more worried about what the massive frames will do in flash lol, hopefully the shrunked image size will help me out.
Rank: OpenGL & Glut "Nub", C++ Freshman.
there is another way,trought WinApi - BitBlt.

To avoid having large jumps between frames use a set interval instead of steady motion from a timer, only moving onto the next frame when the previous one is rendered and saved to disk, or use buffering like kburkhart84 suggests.

The code I gave you is from a class which does exactly what you want (auto-naming) but I ripped it out as I didn't think it would be useful, I'll give you the source presently, just remember that the way I combine the filename and increment variable is a *very* bad way to do it as you could have a filename longer than the buffer which is bad news, it'll work fine for shorter (< 256 including number and extension) names. Really I should convert this to STL but I don't have a lot of spare time so I'll give you it as it stands now:

Header:
/*  Name:        gl_screenshot  Author:      eSCHEn  Contact:     eschen@gibbering.net               http://legion/gibbering.net/evillution/  Date:        16 Jul 04  Copyright:   Use it, have fun.  Author retains copyright.               If you use this in a project then send me a               mail - I'd love to see what you're doing with               it :)               Released under MPL 1.1, see 'mpl.txt' for               details.  Description: Exports a class 'CScreenshot', which takes               a screenshot of an OpenGL context and saves               it to a specified targa file.                 Usage of Class:      ===============  Methods:    CounterPosition -> Current position of internal counter.    SetCounter      -> Sets the initial position of internal counter.    SetFileName     -> Sets the base filename for saving.    TakeScreenshot  -> Takes the screenshot using internal filename.    TakeScreenshot  -> Takes the screenshot, <Filename> overrides internal                       filename.                         Properties:    AutoIncrement -> Set to true if auto-incrementing of counter is needed,                     will add auto-numbering to the end of the internal                     filename.*/struct CTGAHeader_ForSaving{  char IDCount;  char ColorMap;  char ImageCode;  unsigned short CMOrigin;  char CMLength;  char CMBitDepth;  unsigned short IMXOrigin;  unsigned short IMYOrigin;  unsigned short IMWidth;  unsigned short IMHeight;  char IMBitDepth;  char IMDescByte;};class CScreenshot{  public:    // constructor    CScreenshot();    // destructor    virtual ~CScreenshot();        // methods    int CounterPosition();    void SetCounter(int CountValue);    void SetFileName(char* FileName);        void TakeScreenshot();    void TakeScreenshot(char* FileName);    char* LastFilename() {return fLastFileName;};        // variables    bool AutoIncrement; // set to false to stop auto-numbering      private:    // variables    char* fDefaultFileName;    int fFileCount;    char* fLastFileName;};


Source:
#include <windows.h>#include <gl/gl.h>#include <stdio.h>#include "gl_screenshot.h"/* constructor */CScreenshot::CScreenshot(){  // some sensible default parameters  fDefaultFileName = "shot";  AutoIncrement    = true;  fFileCount       = 0;}/* destructor */CScreenshot::~CScreenshot(){  // nowt to do}int CScreenshot::CounterPosition(){  // get current counter position  return fFileCount;}   void CScreenshot::SetCounter(int CountValue){  // copy start position of counter  fFileCount = CountValue;}  void CScreenshot::SetFileName(char* FileName){  // copy filename  fDefaultFileName = FileName;}  void CScreenshot::TakeScreenshot(){  /* grab OpenGL buffer */    int VPort[4],FSize,PackStore;  unsigned char *PStore;  // get viewport dims (x,y,w,h)  glGetIntegerv(GL_VIEWPORT,VPort);    // allocate space for framebuffer in rgb format  FSize = VPort[2]*VPort[3]*3;  PStore = new unsigned char[FSize];  // store unpack settings  glGetIntegerv(GL_PACK_ALIGNMENT, &PackStore);  // setup unpack settings  glPixelStorei(GL_PACK_ALIGNMENT, 1);  // this actually gets the buffer pixels  glReadPixels(VPort[0],VPort[1],VPort[2],VPort[3],GL_RGB,GL_UNSIGNED_BYTE,PStore);  // restore unpack settings  glPixelStorei(GL_PACK_ALIGNMENT, PackStore);          /* dump data to disk */     CTGAHeader_ForSaving Header;    int loop;  FILE *pFile;  unsigned char SwapByte;  char NewFileName[256];    // setup header  Header.IDCount    = 0;  Header.ColorMap   = 0;  Header.ImageCode  = 2;  // uncompressed rgb image  Header.CMOrigin   = 0;  Header.CMLength   = 0;  Header.CMBitDepth = 0;  Header.IMXOrigin  = 0;  Header.IMYOrigin  = 0;  Header.IMWidth    = VPort[2];  Header.IMHeight   = VPort[3];  Header.IMBitDepth = 24; // rgb with no alpha  Header.IMDescByte = 0;    // swap red and blue  for (loop=0;loop<FSize;loop+=3)  {    SwapByte       = PStore[loop];    PStore[loop]   = PStore[loop+2];    PStore[loop+2] = SwapByte;      }    // setup file name  if (AutoIncrement)    sprintf( NewFileName,"%s%d.tga",fDefaultFileName,fFileCount );  else    sprintf( NewFileName,"%s.tga",fDefaultFileName );    // copy filename  fLastFileName = NewFileName;    // open file  pFile = fopen( NewFileName,"wb" );  if (pFile==NULL)  {    delete[] PStore;    return;  }         // write to disk  fwrite( &Header,sizeof(CTGAHeader_ForSaving),1,pFile ); // header  fwrite( PStore,FSize,1,pFile ); // image data    // close file  fclose( pFile );    // free memory  delete[] PStore;    // move to next file counter position  if (AutoIncrement)    fFileCount++;}  void CScreenshot::TakeScreenshot(char* FileName){  // copy filename  fDefaultFileName = FileName;    // call overloaded screenshot method  this->TakeScreenshot();  }


They are both direct ports from my Delphi classes which I did when I started with C++ so they're probably done in a very bad way, I'd be interested if anyone with more experience would care to comment on them as I need to learn as much C++ as possible and I want to do it in the correct way.

Anyway, hope that helps you Zeusbwr.
--
Cheers,
Darren Clark
Quote:Original post by shadowwz
there is another way,trought WinApi - BitBlt.


Below is the Delphi method I use to Grap a screenshot in OpenGL

Notice that you can also grab only part of the screen if needed.

I have No idea how to implement this in c++ :| but this will lead you in the right direction

Oh, this saves to 24 Bit Bitmap, but since you are goin to shrink the stuff in any case, you can just save to whatever format you want.

procedure TRGLBaseScene.ScreenPartToBitmap(const aFileName : String; aX, aY,    aWidth, aHeight : integer);var  lBitmap : TBitmap;begin  lBitmap := TBitmap.Create;  try    lBitmap.Width  := aWidth;    lBitmap.Height := aHeight;    lBitmap.PixelFormat := pf24bit;    BitBlt(lBitmap.Canvas.Handle, 0, 0,            aWidth, aHeight,            DeviceContext, aX, aY, SRCCOPY);    lBitmap.SaveToFile(aFileName);  finally    FreeAndNil(lBitmap);  end;end
Quote:Original post by Zeusbwr
How can i combine the string structure of a name "screenie" with an integer variable, so i can do it like "screenie0", screenie1, screenie2, etc.


// variablesint Frame_Num;char Frame_Name [1024];char Screen_Save_Name [1024];// do this oncestrcpy (Screen_Save_Name, "C:\\ScreenCap Directory\\screenie-");// do this every frame, then use Frame_Name as the file namesprintf (Frame_Name, "%s%.5d.bmp", Screen_Save_Name, Frame_Num++);
or you can save your file name into an array as such..

char file_name[30];

include <string.h> header file.

strcpy(file_name, "screenie");

then in your loop going from 0 - whatever use this as your counter per frame then increment loop each frame. i have actually done this using .bmp's but yes, frame by frame it was really slow. anyway convert the 0 to ascii, using the itoa function, then do a strcat() to append the result onto your string. someones sprintf does the same thing, depending on what the counter is. this is just another way to do it or how i did it for mine.
heh

This topic is closed to new replies.

Advertisement