Jump to content
  • Advertisement
Sign in to follow this  
Zeusbwr

OpenGL Screenies, How To?

This topic is 5025 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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!

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.



// variables
int Frame_Num;
char Frame_Name [1024];
char Screen_Save_Name [1024];

// do this once
strcpy (Screen_Save_Name, "C:\\ScreenCap Directory\\screenie-");

// do this every frame, then use Frame_Name as the file name
sprintf (Frame_Name, "%s%.5d.bmp", Screen_Save_Name, Frame_Num++);

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!