• Advertisement

Archived

This topic is now archived and is closed to further replies.

Screenshots: how to save screen to TGA?

This topic is 5529 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

I need to a make snapshot of my opengl program (a simple 3dplotter i made to see mathematical algorithm outputs) How do i save the screen to a graphic format file? I there any available code to make this? PLEASE?! How can i take the screen matrix pixels to write them to disk? Can anybody help me?? THANKS!!! LOTS OF THANKS!

Share this post


Link to post
Share on other sites
Advertisement
So long as you are using opengl it''s very easy. First you need to have a function to create a tga (or whatever format you want) file. The following is the source for doing just that. This is copied out of OpenGL Game Programming (except the comments), no copyright infringement intended.

int WriteTGAFile(char *filename, short int width, short int
height, unsigned char *imageData)
{
unsigned char byteSkip;
short int shortSkip;
unsigned char imageType;
int colorMode;
unsigned char colorSwap;
int imageIdx;
unsigned char bitDepth;
long imageSize;
FILE *filePtr; // you must #include stdio.h to use
this

filePtr = fopen(filename, "wb");
if (!filePtr)
{
fclose(filePtr);
return 0;
}
imageType = 2; // rgb, uncompressed
bitDepth = 24; // 24 bits per pel
colorMode = 3;
byteSkip = 0;
shortSkip = 0;

fwrite(&byteSkip, sizeof(unsigned char), 1, filePtr);
fwrite(&byteSkip, sizeof(unsigned char), 1, filePtr);
fwrite(&imageType, sizeof(unsigned char), 1, filePtr);
fwrite(&shortSkip, sizeof(short int), 1, filePtr);
fwrite(&shortSkip, sizeof(short int), 1, filePtr);
fwrite(&byteSkip, sizeof(unsigned char), 1, filePtr);
fwrite(&shortSkip, sizeof(short int), 1, filePtr);
fwrite(&shortSkip, sizeof(short int), 1, filePtr);
fwrite(&width, sizeof(short int), 1, filePtr);
fwrite(&height, sizeof(short int), 1, filePtr);
fwrite(&bitDepth, sizeof(unsigned char), 1, filePtr);

imageSize = width * height * colorMode;
for (imageIdx = 0; imageIdx < imageSize; imageIdx +=
colorMode)
{
colorSwap = imageData[imageIdx];
imageData[imageIdx] = imageData[imageIdx + 2];
imageData[imageIdx + 2] = colorSwap;
}

fwrite(imageData, sizeof(unsigned char), imageSize, filePtr);
fclose(filePtr);
return 1;
}

Ok, that will (when you give it some data to work with) write a tga. Now for the function to save the screen. This is again copied out of OpenGL Game Programming. The code here assumes a 800 x 600 window, if you are using one of a different size, change the numbers to match you resolution.

void SaveScreenshot()
{
imageData = malloc(800*600*3);
memset(imageData, 0, 800*600*3);
glReadPixels(0, 0, 799, 599, GL_RGB, GL_UNSIGNED_BYTE,
imageData);

WriteTGAFile("writeout.tga", 800, 600, (unsigned char*)
imageData);

free(imageData);
}

There you go, hope that helps.

Share this post


Link to post
Share on other sites
If you''re using windows, you can use alt-print screen to save a screenshot to the clipboard, and copy the image from your clipboard into any paint program.


Mike

"The state is that great fiction by which everyone tries to live at the expense of everyone else." - Frederick Bastiat, The Law

"Capitalism between consenting adults is a basic human right." - Unknown

Share this post


Link to post
Share on other sites
quote:

glReadPixels(0, 0, 799, 599, GL_RGB, GL_UNSIGNED_BYTE, imageData);


This is not correct if you want to read a 800x600 image. Third and fourth argument should be 800 and 600, since that''s how many pixels you want to read.

Share this post


Link to post
Share on other sites
Thanks!!!!!
This is just i needed!
THANKS Dragon88!

Now i am greedy! I want to know if there is a rutine availaible which writes PNG graphics files...

Thanks!



Share this post


Link to post
Share on other sites
quote:
Original post by Brother Bob
[quote]
glReadPixels(0, 0, 799, 599, GL_RGB, GL_UNSIGNED_BYTE, imageData);


This is not correct if you want to read a 800x600 image. Third and fourth argument should be 800 and 600, since that''s how many pixels you want to read.

You''re forgetting something. Computers count zero as the first number, thus starting at 0, 0, and copying through 799, 599 will encompass exactly 800x600 pixels.

Share this post


Link to post
Share on other sites
Nope, 800 and 600 are the width and height of the image, not the coordinates of the opposite corner.

From the OpenGL specification:

void ReadPixels( int x, int y, sizei width, sizei height,
enum format, enum type, void *data );

... width and height are the width and height, respectively, of the pixel rectangle to be
drawn.

Share this post


Link to post
Share on other sites
Way to go on correcting the guy four times in a row. Talk about redundancy. Did by some chance you all posted in unison, or did the last few of you not bother to read the rest of the posts cuz you wanted to put your 2 cents in?

How come more people don''t use DevIL/OpenIL? It''s got fancy features that do stuff like this, along with tons of really cool imaging effects.

Share this post


Link to post
Share on other sites
quote:

You''re forgetting something. Computers count zero as the first number, thus starting at 0, 0, and copying through 799, 599 will encompass exactly 800x600 pixels.


You''re the one that''s forgetting something; whate the third and fourth parameter really mean. They mean width and height of the image you want to read. So 800 and 600 will read a 800x600 image.

Share this post


Link to post
Share on other sites
I copied the code to my proyect...
This routine have the funny misbehaviour of inverting RGB colors..
The colors are ordered RBG instead of RGB...
I fixed change WriteTGAFile() as follow:
(correct)
for (imageIdx = 0; imageIdx < imageSize; imageIdx +=colorMode)
{
colorSwap = imageData[imageIdx];
imageData[imageIdx ] = imageData[imageIdx + 2];
imageData[imageIdx + 2] = imageData[imageIdx + 1];
imageData[imageIdx + 1] = colorSwap;
}

Instead of:
(wrong)
for (imageIdx = 0; imageIdx < imageSize; imageIdx +=colorMode)
{
colorSwap = imageData[imageIdx];
imageData[imageIdx] = imageData[imageIdx + 2];
imageData[imageIdx + 2] = colorSwap;
}

Share this post


Link to post
Share on other sites
I want to know if there is a rutine availaible which writes PNG graphics files...

Share this post


Link to post
Share on other sites
Ehm... try using libpng... PNG files are quite complicated; libpng requires zlib (zip files).

ZLIB
LibPNG

Using libpng is quite a pain in the ass if you''re as stupid as me, so DevIL might do the job:

DevIL

DevIL loads about every image file imaginable, and it can save them too.

Share this post


Link to post
Share on other sites
I''m afraid I have to make another question.

When I dump my TGA file by means of glReadPixels and WriteTGAFile, I get an image with the red component shifted one pixel to the left (i.e. the red component for the pixel at (x,y) is assigned to de pixel at (x-1,y) where x grows to the right).

This behavior does not depend on the color reordering applied to imageData on WriteTGAFile.

Any clues?

Share this post


Link to post
Share on other sites
I fixed the red shi(f)t problem.

I added a byteSkip to the header and no longer need the color reordering loop at the begining of WriteTGAFile function.

Thanks for all the support.

Share this post


Link to post
Share on other sites
Fine, Brother Bob, maybe you're right. I can't say, I just wrote what I saw in the book, you want to correct it, you call the publisher.

[edited by - Dragon88 on December 2, 2002 10:18:32 PM]

Share this post


Link to post
Share on other sites
actually, libpng is pretty easy to use. it comes with a readme file that explains in great detail how to load and save images using the library. here''s the code i use to load and save bitmaps, optionally using my custom win32 file i/o functions:

  
BOOL g_PngRes;
void PngErrorHandler(png_structp png_ptr, png_const_charp message)
{
g_PngRes = FALSE;
OutputDebugString(message);
}

BOOL WINAPI ImgSaveBitmapAsPng(HBITMAP Bitmap, LPCTSTR FileName)
{
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, PngErrorHandler, 0);
if (!png_ptr) {
SetLastError(ERROR_OUTOFMEMORY);
return FALSE;
}

png_infop info_ptr = 0;
#ifdef USE_WIN32_IO
HANDLE hFile = INVALID_HANDLE_VALUE;
#else
FILE *fp = 0;
#endif

g_PngRes = TRUE;
SetLastError(NO_ERROR);

#ifdef USE_WIN32_IO
hFile = CreateFile(FileName, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
goto cleanup;
#else
fp = _tfopen(FileName, _T("wb"));
if (!fp)
goto cleanup;
#endif

info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
goto cleanup;

#pragma warning (push)
#pragma warning (disable: 4611) // interaction between ''_setjmp'' and C++ object destruction is non-portable

if (setjmp(png_jmpbuf(png_ptr)))
goto cleanup;
#pragma warning (pop)

#ifdef USE_WIN32_IO
png_init_write_io_win32(png_ptr, hFile);
#else
png_init_io(png_ptr, fp);
#endif

BITMAP bm;
GetObject(Bitmap, sizeof(BITMAP), &bm);

png_set_IHDR(png_ptr, info_ptr, bm.bmWidth, bm.bmHeight, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
if (!g_PngRes)
goto cleanup;
png_write_info(png_ptr, info_ptr);
if (!g_PngRes)
goto cleanup;
png_set_bgr(png_ptr);
if (!g_PngRes)
goto cleanup;

for (int i = 0; i < bm.bmHeight; ++i)
{
png_write_row(png_ptr, PBYTE(bm.bmBits)+i*bm.bmWidthBytes);
if (!g_PngRes)
goto cleanup;
}

png_write_end(png_ptr, 0);

cleanup:
#ifdef USE_WIN32_IO
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
#else
if (fp)
fclose(fp);
#endif

png_destroy_write_struct(&png_ptr, &info_ptr);

if (!g_PngRes) {
DeleteFile(FileName);
}

return g_PngRes;
}

HBITMAP WINAPI ImgLoadPngToBitmap(LPCTSTR FileName)
{
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, PngErrorHandler, 0);
if (!png_ptr) {
SetLastError(ERROR_OUTOFMEMORY);
return FALSE;
}

png_infop info_ptr = 0;
#ifdef USE_WIN32_IO
HANDLE hFile = INVALID_HANDLE_VALUE;
#else
FILE *fp = 0;
#endif

g_PngRes = TRUE;
SetLastError(NO_ERROR);
HBITMAP Bitmap = 0;

#ifdef USE_WIN32_IO
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (hFile == INVALID_HANDLE_VALUE)
goto cleanup;
#else
fp = _tfopen(FileName, _T("rb"));
if (!fp)
goto cleanup;
#endif

info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
goto cleanup;

#pragma warning (push)
#pragma warning (disable: 4611) // interaction between ''_setjmp'' and C++ object destruction is non-portable

if (setjmp(png_jmpbuf(png_ptr)))
goto cleanup;
#pragma warning (pop)

#ifdef USE_WIN32_IO
png_init_read_io_win32(png_ptr, hFile);
#else
png_init_io(png_ptr, fp);
#endif

png_read_info(png_ptr, info_ptr);
if (!g_PngRes)
goto cleanup;

BITMAPINFO bi;
ZeroMemory(&bi, sizeof(BITMAPINFO));

int bit_depth, color_type, interlace_method, compression_method, filter_method;
png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) &bi.bmiHeader.biWidth, (png_uint_32 *) &bi.bmiHeader.biHeight, &bit_depth, &color_type, &interlace_method, &compression_method, &filter_method);
if (!g_PngRes)
goto cleanup;

bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = (color_type == PNG_COLOR_TYPE_RGB_ALPHA ? 32 : 24);
bi.bmiHeader.biCompression = BI_RGB;
PVOID Bits;
Bitmap = CreateDIBSection(0, &bi, DIB_RGB_COLORS, &Bits, 0, 0);
if (!Bitmap)
goto cleanup;
BITMAP bm;
GetObject(Bitmap, sizeof(BITMAP), &bm);

png_set_bgr(png_ptr);
if (!g_PngRes)
goto cleanup;

for (int i = 0; i < bi.bmiHeader.biHeight; ++i)
{
png_read_row(png_ptr, PBYTE(Bits)+i*bm.bmWidthBytes, 0);
if (!g_PngRes)
goto cleanup;
}

png_read_end(png_ptr, 0);

cleanup:
#ifdef USE_WIN32_IO
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
#else
if (fp)
fclose(fp);
#endif

png_destroy_read_struct(&png_ptr, &info_ptr, 0);

if (!g_PngRes && Bitmap) {
DeleteObject(Bitmap);
Bitmap = 0;
}
return Bitmap;
}

Share this post


Link to post
Share on other sites
quote:

Fine, Brother Bob, maybe you''re right. I can''t say, I just wrote what I saw in the book, you want to correct it, you call the publisher.


I don''t know what book it is, so can''t tell the publisher. Feel free to post here what the book says. Both the Red Book and the OpenGL specification is pretty clear on this point.

If you were correct, why does this piece of code crach horribly (access violation, could not write to memory) with a HUGE buffer overrun? It craches on glReadPixels. It is placed directly after the display function.

  
GLubyte *buf = new GLubyte[4];
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(300, 300, 301, 301, GL_LUMINANCE, GL_UNSIGNED_BYTE, buf);
delete [] buf;

According to you, it should only read a 2x2 pixel area, starting at 300x300. When I replace 301 with 2, I get correct behaviour, and the result in the buffer is correct (yes, I have verified with what I have drawn at that 2x2 squard).

Share this post


Link to post
Share on other sites
Well, all I can say is that when I use the function with the third and forth arguments as 799 and 599 (respectivly), I end up with an image that is 800x600, according to my image viewer. Why you have a memory leak is a mystery to me.

Conclusion: 799 and 599 work fine for me.

Share this post


Link to post
Share on other sites
I must say that that is very odd. What hardware/driver are you using? If possible, can you send me a program that works for you so I can try it here? Whether it''s a Win32 executable or source doesn''t matter. You find my address in my profile.

Share this post


Link to post
Share on other sites
quote:
Original post by Brother Bob
GLubyte *buf = new GLubyte[4];
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(300, 300, 301, 301, GL_LUMINANCE, GL_UNSIGNED_BYTE, buf);
delete [] buf;

According to you, it should only read a 2x2 pixel area, starting at 300x300. When I replace 301 with 2, I get correct behaviour, and the result in the buffer is correct (yes, I have verified with what I have drawn at that 2x2 squard).


That glReadPixels line will read from the square with top-left coordinates (300,300) and a width of 302x302. The original code from OGLGP of:

glReadPixels(0, 0, 799, 599, ...)

will read from the rectangle with top-left coordinates (0,0) and a width of 800x600.



Kevin "Khawk" Hawkins
CEO and News Director, GameDev.net
Software Engineer, Raydon Corporation
Author, OpenGL Game Programming
Developer Diary

Share this post


Link to post
Share on other sites
You are wrong, Khawk. From MSDN:

void glReadPixels(
GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
GLvoid *pixels
);

width, height - The dimensions of the pixel rectangle. The width and height parameters of one correspond to a single pixel.


Really, it's not hard to figure out even without a reference. Why would those parameters be width-1 and height-1? According to you, they are. At least Dragon88 was thinking logically, even if he was wrong.

~CGameProgrammer( );

EDIT: Even the Red Book (OGLPG) says on page 296 that those parameters are the width and height of the array of pixels to read.

[edited by - CGameProgrammer on December 3, 2002 12:19:34 AM]

Share this post


Link to post
Share on other sites
Kevin, by OGLPG I assume you mean your book, OpenGL Game Programming. I had a look at the source for chapter 7, and it does not work correct. Sure, it looks like it works, but in this case you're lucky you can't see what's wrong, at least not by looking at the screenshot.

When you allocate memory, you allocate a 800x600 block and you clear with a black color. When saving, you're reading only 799x599 pixels, leaving the upper row and right column untouched. Because the image background is also black, you won't see anything. But, reset the memory with 0xFF instead of 0, and you will see a white border in the upper and right edge in the screenshot. You can also change the clear color in OpenGL and see that the edge has a different color than the background.

It can also be seen by changing the pack alignment right before glReadPixels. The default pack alignment is 4, and 800 is a multiple of 4. So if you only read 799 pixels per row, OpenGL will move the "store pointer" to next multiple of 4 (*) at the end of each row, which is 800, where it will store the next row. Change the pack alignment to 1 and the screenshot will look 45 degrees skewed.



(*) It's not the actual address that is rounded up to nearest multiple of 4, but the offset from the start address. But assuming the start address is a multiple of 4, it is the same thing.

[edited by - Brother Bob on December 4, 2002 7:18:15 AM]

Share this post


Link to post
Share on other sites
quote:
Original post by CGameProgrammer
Really, it''s not hard to figure out even without a reference. Why would those parameters be width-1 and height-1? According to you, they are. At least Dragon88 was thinking logically, even if he was wrong.

~CGameProgrammer( );

EDIT: Even the Red Book (OGLPG) says on page 296 that those parameters are the width and height of the array of pixels to read.

[edited by - CGameProgrammer on December 3, 2002 12:19:34 AM]



Yes, I know they''re the width and height. I don''t know where you got the impression that I was saying it wasn''t the width and height.

The reason they are width-1 and height-1 was (from what I remember when doing the book/code) because values of (0,0,800,600) did not work.

The explanation from Brother Bob is more valid, and he''s probably correct (I can''t check myself now). The book has plenty of minor defects like this, and we''re always documenting them for the revision.



Kevin "Khawk" Hawkins
CEO and News Director, GameDev.net
Software Engineer, Raydon Corporation
Author, OpenGL Game Programming
Developer Diary

Share this post


Link to post
Share on other sites
When you say "OGLPG" you mean the OpenGL Programming Guide, right? aka the Red Book?

Also you said values of 301,301 specify a 302x302 area, indicating you felt they were really width-1 and height-1. Not only does that make zero logical sense, but all documents I have found indicate it is width and height. If your code did not work, I would guess it is the fault of some other part.

~CGameProgrammer( );

Share this post


Link to post
Share on other sites
quote:
Original post by Khawk
Author, OpenGL Game Programming



I used OGLGP, not OGLPG.

I also admitted the code in the book is wrong. Bob has a valid point in that the memory buffer is not completely filled by the glReadPixels function because of the (799, 599) when the memory buffer is 800x600. For whatever reason I did that (it was written about 2 years ago - I can't remember), it's not 100% accurate - it will work and not crash, but it doesn't complete its desired functionality of giving the entire 800x600 client window area.

Share this post


Link to post
Share on other sites

  • Advertisement