PDA

View Full Version : This is easiest way to make screenshoots of your game.



JernejL
22-02-2007, 01:26 PM
Need some fast code to dump whole screenshoot of your opengl program to TGA image?

No problem!

with some small changes it would save to bmp as well, if you like png or jpg you'll have to extend that yourself :(


// this saves the viewport to a TGA file format
Procedure ScreenShoot(filen: string);

type
TTGAHeader = packed record
Length: Byte;
ColorMapType: Byte;
ImageType: Byte;
FirstEntryIndex: Word;
ColorMapLength: Word; // Palette Length - should be alaways 256
ColorMapEntrySize: Byte; // Palette BPP
XOriginImage: Word;
YOriginImage: Word;
ImageWidth: Word;
ImageHeight: Word;
PixelDepth: Byte;
ImageDescriptor: Byte; // AlphaBits (for example - 8 bits for 32 bit tga)
end; // 18

var
vport: array[0..3] of integer;
buffer: pchar;
bufflen: integer;
f: Tmemorystream;
TGAHeader: TTGAHeader;
begin
glGetIntegerv(GL_VIEWPORT, @vPort);

bufflen:= (vport[2] * vport[3]) * 3; // rgb
getmem(buffer, bufflen);

glPixelStorei(GL_PACK_ALIGNMENT, 1);

glReadPixels(0, 0, vport[2], vport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, buffer);

fillchar(TGAHeader, sizeof(TGAHeader), 0);
TGAHeader.ImageType:= 2;
TGAHeader.ImageWidth:= vport[2];
TGAHeader.ImageHeight:= vport[3];
TGAHeader.PixelDepth:= 24;

f:= Tmemorystream.Create;
f.Write(TGAHeader, sizeof(TGAHeader));
f.Write(buffer^, bufflen);
f.SaveToFile(filen);
f.free;

freemem(buffer);

end;

cairnswm
22-02-2007, 04:12 PM
Very Cool

I had a few problems through:

f: Tfile;
TGAHeader: Tmemorystream;

changes to
f: Tmemorystream;
TGAHeader: TTGAHeader;

And then it worked right first time.

Thanks for this. (Already added to S2DL - will upload new S2DL later tonight)

3_of_8
22-02-2007, 04:17 PM
Well, TGA has the enormous advantage of an origin at the bottom left - just like OpenGL. BMP and JPG do not have this advantage.

I wrote something to make it work for SDL and BMP:


type
TRGB=record
R, G, B: Byte;
end;
PRGB=^TRGB;
var
viewport: array[0..3] of Integer;
buffer: Pointer;
size: Integer;
Surface: PSDL_Surface;
I, J: Integer;
begin
glGetIntegerv(GL_VIEWPORT, @viewport);
size:=viewport[2]*viewport[3]*3;
GetMem(buffer, size);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
GL_RGB, GL_UNSIGNED_BYTE, buffer);
Surface:=SDL_CreateRGBSurface(0, viewport[2], viewport[3],
24, $FF, $FF00, $FF0000, $00);
for I:=0 to viewport[3]-1 do
for J:=0 to viewport[2]-1 do
PRGB(Integer(Surface.pixels)+I*viewport[2]*3+J*3)^:=
PRGB(Integer(buffer)+(viewport[3]-I-1)*viewport[2]*3+J*3)^;
SDL_SaveBMP(Surface, PChar(AFilename));
SDL_FreeSurface(Surface);
end;

JernejL
22-02-2007, 05:09 PM
Very Cool

I had a few problems through:

f: Tfile;
TGAHeader: Tmemorystream;

changes to
f: Tmemorystream;
TGAHeader: TTGAHeader;


yeah, sorry, i use Tfile class, which is compatible with Tmemorystream, so i just wanted to rename the variable types and seems i made an error, i'll edit my post to correct this.

3_of_8: BMP has also a top-left origin, and also doesn't need any encoding.

raw formats such as tga and bmp has advantage over jpeg and png, because they don't need encoding, that makes them simplier to implement.

3_of_8
22-02-2007, 05:49 PM
TGA is not necessarily raw. It can also have RLE.

And yes, BMP has the origin at the TOP left, but OpenGL at the BOTTOM left. Therefore, with your method, a BMP screenshot is upside down and you do need to encode it. Believe me, I've tried it out not two hours ago.

JernejL
22-02-2007, 06:14 PM
TGA is not necessarily raw. It can also have RLE.

And yes, BMP has the origin at the TOP left, but OpenGL at the BOTTOM left. Therefore, with your method, a BMP screenshot is upside down and you do need to encode it. Believe me, I've tried it out not two hours ago.

BMP has a bottom left origin, i have no idea what you did, but bmp raw data IS upside down in the file.

edit: see:
http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html

JernejL
22-02-2007, 06:40 PM
Here's a working BMP saving variant:


Procedure ScreenShoot(filen: string);

// 54 bytes bmp header
TBMPheader = packed record
magic : word; // BM (word: 19778)
headersize : longword; // 54
reserved : longword; // 0
dataofs : longword; // 54

Size : longword;
Width : longword;
Height : longword;
Planes : word; // 1
BitCount : word; // 24
Compression : longword; // 0
SizeImage : longword; // w * h * 3
XPelsPerMeter : longword; // 36100
YPelsPerMeter : longword; // 36100
ClrUsed : longword; // 0
ClrImportant : longword; // 0
end;

var
vport: array[0..3] of integer;
buffer: pchar;
bufflen: integer;
f: Tmemorystream;
TGAHeader: TTGAHeader;
BMPheader: TBMPheader;
begin
glGetIntegerv(GL_VIEWPORT, @vPort);

bufflen:= (vport[2] * vport[3]) * 3; // rgb
getmem(buffer, bufflen);

glPixelStorei(GL_PACK_ALIGNMENT, 1);

glReadPixels(0, 0, vport[2], vport[3], GL_BGR_EXT, GL_UNSIGNED_BYTE, buffer);

fillchar(BMPheader, sizeof(BMPheader), 0);

BMPheader.magic := 19778;
BMPheader.headersize := 54;
BMPheader.reserved := 0;
BMPheader.dataofs := 54;
BMPheader.Size := 40;
BMPheader.Width := vport[2];
BMPheader.Height := vport[3];
BMPheader.Planes := 1;
BMPheader.BitCount := 24;
BMPheader.SizeImage := bufflen;
BMPheader.XPelsPerMeter := 36100;
BMPheader.YPelsPerMeter := 36100;
BMPheader.ClrUsed := 0;
BMPheader.ClrImportant := 0;

f:= Tmemorystream.Create;
f.Write(BMPheader, sizeof(BMPheader));
f.Write(buffer^, bufflen);
f.SaveToFile(filen);
f.free;

freemem(buffer);

end;

cairnswm
22-02-2007, 07:34 PM
How do you get the info from a stream into a TBitmap object?

If we could do this then you can use the more standard Delphi calls to save JPEG and PNG.

3_of_8
22-02-2007, 07:50 PM
I did never say anything else. BMP origin is on the top left but OpenGL origin is on the bottom left.

I used SDL_SaveBMP and it was indeed upside down. So, for anyone who wants to use SDL, my solution is necessary.

Galfar
22-02-2007, 07:55 PM
BMP has a bottom left origin ...

BMP has the origin at the TOP left ...
Bitmap can have TOP-left origin as well as BOTTOM-left origin. Width and Height fields in the BMP header should be signed integers not unsigned. Then if Height is positive BMP is bottom-up and when Height is negative then BMP is considered top-down.


biHeight -
Specifies the height of the bitmap, in pixels. If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.
If biHeight is negative, indicating a top-down DIB, biCompression must be either BI_RGB or BI_BITFIELDS. Top-down DIBs cannot be compressed.

JernejL
22-02-2007, 08:09 PM
How do you get the info from a stream into a TBitmap object?

If we could do this then you can use the more standard Delphi calls to save JPEG and PNG.

i don't use VCL, i have a bmp header structure record and fill it and write the file manually.

cairnswm
22-02-2007, 08:11 PM
How do you get the info from a stream into a TBitmap object?

If we could do this then you can use the more standard Delphi calls to save JPEG and PNG.

i don't use VCL, i have a bmp header structure record and fill it and write the file manually.

I know. But can you get it into a TBitmap object?

jdarling
22-02-2007, 09:02 PM
Don't forget that if your in SDL mode you can't use the methods Delfi has provided, instead you have to use SDL_SaveBMP()

So, I've set my code up so that it checks a global InOpenGL flag and then calls the proper storage routine :)

JernejL
22-02-2007, 09:12 PM
Don't forget that if your in SDL mode you can't use the methods Delfi has provided, instead you have to use SDL_SaveBMP()

So, I've set my code up so that it checks a global InOpenGL flag and then calls the proper storage routine :)

what has that to do with anything? o_O the code will work independently of SDL, VCL or any other library you can find out there, it will work if you even compile it into a DLL and inject it into quake3 or something like that, it uses it's own, pure opengl and just Tmemorystream routines to make a bitmap and save it to a file...

cairnswm
23-02-2007, 05:37 AM
If using SDL and OpenGL you may not actually be in OpenGL - in which case the SDL_SaveBMP is needed instead :)

JernejL
23-02-2007, 08:08 AM
If using SDL and OpenGL you may not actually be in OpenGL - in which case the SDL_SaveBMP is needed instead :)

not sure what you mean.. if you have a sdl opengl ap running and call that code, it will work.

Nitrogen
23-02-2007, 08:09 AM
Hate to be a smartass here, but

Does anyone know that if you press Alt-PrtScn
You only get a picture of the current foreground window, instead of the whole screen? :)

cairnswm
23-02-2007, 08:28 AM
If using SDL and OpenGL you may not actually be in OpenGL - in which case the SDL_SaveBMP is needed instead :)

not sure what you mean.. if you have a sdl opengl ap running and call that code, it will work.

But if its not OpenGL? This solution works for OpenGL windows - not SDL windows (unless using OpenGL)

cairnswm
23-02-2007, 08:29 AM
Hate to be a smartass here, but

Does anyone know that if you press Alt-PrtScn
You only get a picture of the current foreground window, instead of the whole screen? :)

But this puts it in a file. Also excludes the Title bar and frame if a windowed app :)

So while running the game you can make multiple game shots :)

JernejL
23-02-2007, 11:49 AM
Nitrogen:

Yeah i know, but that's not practical for a game use, a game needs it's own screenshoot saving routine, which can have various use, either for graphical effects or just to let players save screenshoots of various moments in your game.

Clipboard isn't portable between platforms, and can hold only 1 screenshoot, also the user has to save it to disk himself.

cairnswm:

It is not meant to be used with just SDL, it is meant for opengl (and sdl with opengl), i never said it is for SDL :/

WILL
01-05-2009, 01:39 AM
I've been trying to make your code support PNG. Well, let me tell you. :) It's pretty much it's own function altogether. :P

Here is the documentation I've been using: http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html

I've got a little CRC function to handle calculating CRC for each chunk. The only thing I'm stuck on is how to encode the IDAT chunk data. I'm not 100% sure, but I believe that I have to drop in 1 byte before each scanline of image data specifying it's filter method. THEN I take that data and lzh encode it and then do my CRC then store it. *whew* This format doesn't mess around. ;)

Can someone let me know if I'm on the right track or at least point me in the right direction. Considering that I'm using JEDI-SDL and OpenGL I'm trying to think of how I'm going to do the compression now.

JernejL
01-05-2009, 09:04 PM
Are you using compression or just store uncompressed pngs?

WILL
02-05-2009, 01:25 PM
Are you using compression or just store uncompressed pngs?

Well I'd opt to doing them uncompressed just to get it working then add compression next to make my function practical for use in my projects.

paul_nicholls
04-05-2009, 03:54 AM
If it helps, I have some code that creates uncompressed .tga files from a buffer in memory...

It expects a image pixel buffer in bgr format like so as default


TSomeBuffer = packed record
b,g,r : Byte;
End;


but you can use the ASwapPixels param to use r,g,b instead.

You can also make it output the pixel rows in reverse (AFlipVertically) if you need to, if the image ends upside down.

Unit tga_unit;
{$IFDEF fpc}
{$MODE Delphi}
{$ENDIF}
{$H+}
Interface

Type
PByteArray = ^TByteArray;

Function WriteTGA(Const AFileName : AnsiString;
Const AImageWidth,AImageHeight : Integer;
Const AImageBuffer : PByteArray;
Const ASwapPixels : Boolean;
Const AFlipVertically : Boolean) : Boolean;

implementation

Uses
Classes;

Function WriteTGA(Const AFileName : AnsiString;
Const AImageWidth,AImageHeight : Integer;
Const AImageBuffer : PByteArray;
Const ASwapPixels : Boolean;
Const AFlipVertically : Boolean) : Boolean;
Const
TGAHeader : Packed Array[0..12 - 1] Of Byte = (0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0);

Var
TGAFile : TFileStream;
Header : Packed Array[0..6 - 1] Of Byte;
Bits : Byte;
ColorMode : Integer;
TempColor : Byte;
index : Integer;
Row : Integer;
Begin
// Open file for output.
TGAFile := TFileStream.Create(AFileName,fmCreate);

// Set the color mode, and the bit depth.
ColorMode := 3;
Bits := 24;

Header[0] := AImageWidth Mod 256;
Header[1] := AImageWidth Div 256;
Header[2] := AImageHeight Mod 256;
Header[3] := AImageHeight Div 256;
Header[4] := Bits;
Header[5] := 0;

TGAFile.Write(TGAHeader,SizeOf(tgaHeader));
TGAFile.Write(Header ,SizeOf(Header));

If ASwapPixels Then
Begin
// Now switch image from RGB to BGR.
index := 0;
While index < (AImageWidth * AImageHeight * ColorMode) Do
Begin
TempColor := AImageBuffer^[index + 0];
AImageBuffer^[index + 0] := AImageBuffer^[index + 2];
AImageBuffer^[index + 2] := TempColor;
Inc(index,ColorMode);
End;
End;

// Finally write the image.
If Not AFlipVertically Then
TGAFile.Write(AImageBuffer^,AImageWidth * AImageHeight * ColorMode)
Else
// Write pixels in reverse row order
Begin
For Row := AImageHeight - 1 Downto 0 Do
TGAFile.Write(AImageBuffer^[Row * AImageWidth * ColorMode],AImageWidth * ColorMode);
End;

If ASwapPixels Then
Begin
// Now switch image from BGR back to RGB.
index := 0;
While index < (AImageWidth * AImageHeight * ColorMode) Do
Begin
TempColor := AImageBuffer^[index + 0];
AImageBuffer^[index + 0] := AImageBuffer^[index + 2];
AImageBuffer^[index + 2] := TempColor;
Inc(index,ColorMode);
End;
End;

// close the file.
TGAFile.Free;
Result := True;
End;

end.


cheers,
Paul