Thread: Capturing Screen w/GDI (8 BPP)

  1. #1
    Registered User
    Join Date
    May 2007
    Posts
    6

    Capturing Screen w/GDI (8 BPP)

    Hi all ! I'm new to the forum. I've registered because this is getting to my nerves, and I was hoping to get a helping hand.

    I'm trying to capture my desktop screen using WinGDI and C++ and then save it to disk as a. bmp file (kind of what you get when you PrntScreen + Paste in Paint). I've manged to do this in 16, 24 and 32 bits per pixel mode, but it doesn't seem to be working in 8 BPP mode: all I was able to make is a crappy .bmp with mixed up colors.

    I've already tried to get the system palette (GetSystemPaletteEntries()) to make my own Identity Palette (including those 20 Window's reserved colors) and selected it into my drawing DC (the one I BitBlt in) and fwrite this palette in the final .bmp file's header, but to no avail.

    Theorically, if you call CreateDIBSection with DIB_PAL_COLORS in 256 colors mode, the RGB palette defined in the RGBQUAD struct included in the BITMAPINFOHEADER is used to draw and interpret the bitmap selected in the DC, but... how could I possibly know which colors are going to be needed to capture my screen ? Moreover, doesn't the actual logical palette depend on which window is on top (aka, have focus)?

    Perhaps, I'm getting it all wrong... or, maybe there's no way of achieving this using GDI.

    Any help will be appreciated.

  2. #2
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    If you are attempting to convert a 32, 24, or 16 bit image into an 8-bit palettized image you will need some type of color conversion algo to convert the millions of colors into 256 distinct colors that approximate the original image.

    Otherwise you will get nothing but garbly gook which sounds exactly like what you are getting.

  3. #3
    Registered User
    Join Date
    May 2007
    Posts
    6
    No, I am not trying to convert images. I'm trying to capture the screen while in 256 colors mode and make a .bmp file that has the same colors that appear on my screen.

    Here's what I'm getting:

    Original screen
    http://img249.imageshack.us/img249/9496/originalbe5.jpg

    Taken with my C++ project
    http://img440.imageshack.us/img440/773/projectjt3.jpg

    If you need to see some of my code, let me know.

  4. #4
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    You may have the byte order back to front - it should be bgr and not rgb, if I recall correctly(see RGBQUAD). It would certainly be worthwhile to check the byte order anyway since it seems that you're getting the right image but with the wrong colours, although that might equally imply a problem with the palette - check the palette entries, too, to ensure they are what you think they should be.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  5. #5
    Registered User
    Join Date
    May 2007
    Posts
    6
    Quote Originally Posted by Ken Fitlike View Post
    check the palette entries, too, to ensure they are what you think they should be.
    That's exactly the point. I don't know which palette entries should be in my own palette, because I can't tell what's going to be on the screen at the moment of capturing it.

    Regarding the RGB/BGR order, the call to GetSystemPalette() returns a PALETTEENTRY struct with red, blue and green attributes already filled. Unless they are returned in the wrong order, my guess is that that's not the real problem. I'll check it anyway, of course.
    Last edited by nfantone; 05-04-2007 at 03:50 PM.

  6. #6
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    If you are in 256 color mode, Windows will set the palette to the palette in the header for the bmp file when it loads the image. If you are attempting to capture a 32 bit color image in 256 colors then you must do what I posted.

    Nothing can be displayed correctly in 256 color mode without having a palette. So if Windows is in 256 color mode, it has a palette.

    What I think you are trying to do is capture a 16 or 24 bpp image from Windows and save it as 256 color bmp. You cannot do this since the millions of colors in the image do not correspond to a palette. Each pixel in these modes has it's own RGB values whereas in palettized mode each pixel is a palette number that corresponds to a table of RGB entries.

  7. #7
    Registered User
    Join Date
    May 2007
    Posts
    6
    Quote Originally Posted by Bubba View Post
    If you are in 256 color mode, Windows will set the palette to the palette in the header for the bmp file when it loads the image.
    That is partially correct. Yes, you need a palette in the header of the bmp for displaying purposes; and no, Windows doesn't set it there for you: you need to do it yourself. And that's exactly what I'm missing: I can't tell which palette fwrite to my .bmp file. What I want is screenshot from my desktop, and in order to get that, once I have a pointer to the bitmap bits, I need to create the .bmp file. A .bmp file has a 54 byte HEADER, an optional palette (only used in 8bpp mode) and its data (RGB entries or, as you pointed out, indexes to a palette). Here's an example taken from my project:

    Code:
            fwrite(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
    	fwrite(&(pBitmapInfo->bmiHeader), sizeof(BITMAPINFOHEADER), 1, fp);
    	fwrite(&(pBitmapInfo->bmiColors), sizeof(RGBQUAD)*256, 1, fp);
    	fwrite(pSBBits, getSize(), 1, fp);
    That bmiColors struct contains the 256 color palette that the indexes will refer to. Which 256 colors should be in there, that I don't know...

    Quote Originally Posted by Bubba View Post
    If you are attempting to capture a 32 bit color image in 256 colors then you must do what I posted.

    What I think you are trying to do is capture a 16 or 24 bpp image from Windows and save it as 256 color bmp.
    I am not trying to convert images formats from 16 or 24 to 8 bpp, or take a screenshot and then save it as a 256 colors bitmap. All of this needs to be done in 8 bits mode (both, the capture and the saving).

  8. #8
    Registered User
    Join Date
    Dec 2006
    Location
    Scranton, Pa
    Posts
    252
    The below is not my code, I found it some time ago on this board (I think one of the senior members post it). I remember testing it and it worked fine;

    Code:
    void CaptureWindow ( HWND hwnd )
    {
        RECT            rect;
    	HDC				src_hdc;
    	HDC				dst_hdc;
    	HBITMAP			hbitmap;
    	BITMAPINFO		bi;
    	LPBYTE			pBits;
    	int				w;
    	int				h;
    	HANDLE			file_handle;
    
    	GetClientRect ( hwnd, &rect );
    
    	w = rect.right - rect.left;
    	h = rect.bottom - rect.top;
    
    	src_hdc = GetDC ( hwnd );
    
    	dst_hdc = CreateCompatibleDC ( src_hdc );
    
    	ZeroMemory ( &bi, sizeof ( bi ) );
    	bi.bmiHeader.biSize = sizeof ( bi.bmiHeader );
    	bi.bmiHeader.biHeight = h;
    	bi.bmiHeader.biWidth = w;
    	bi.bmiHeader.biPlanes = 1;
    	bi.bmiHeader.biBitCount = 24;
    	bi.bmiHeader.biCompression = BI_RGB;
    	bi.bmiHeader.biSizeImage = ((w * bi.bmiHeader.biBitCount +31)& ~31) / 8 * h; 
    
    	hbitmap = CreateDIBSection ( src_hdc, &bi, DIB_RGB_COLORS, (LPVOID *) &pBits, NULL, 0 );
    
    	SelectObject ( dst_hdc, hbitmap );		
    
    	if ( BitBlt ( dst_hdc, 0, 0, w, h, src_hdc, rect.left, rect.top, SRCCOPY ) ) {
    
    	  	file_handle = CreateFile ( "grab.bmp", GENERIC_WRITE, FILE_SHARE_WRITE,
            NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );	
    
    		if ( file_handle != INVALID_HANDLE_VALUE ) {
    			DWORD				dwRet = 0;
    			BITMAPFILEHEADER	bfh;
    
    			ZeroMemory ( &bfh, sizeof ( bfh ) );
    			bfh.bfType = 0x4D42;
    			bfh.bfOffBits = sizeof ( BITMAPFILEHEADER ) + sizeof ( BITMAPINFOHEADER );
    			bfh.bfSize = bi.bmiHeader.biSizeImage + bfh.bfOffBits;
    
    	 		WriteFile ( file_handle, &bfh, sizeof ( bfh ), &dwRet, NULL);
    			WriteFile ( file_handle, &bi.bmiHeader, sizeof ( bi.bmiHeader ), &dwRet, NULL );
    			WriteFile ( file_handle, pBits, bi.bmiHeader.biSizeImage, &dwRet, NULL );
    		}
    
    		CloseHandle ( file_handle );
    		DeleteDC ( dst_hdc );
    		DeleteObject ( hbitmap );
       		ReleaseDC ( hwnd, src_hdc );
    	}
    }
    If I remember, it was something like CaptureWindow(GetDesktopWindow()); to get the desktop.

  9. #9
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Windows doesn't set it there for you: you need to do it yourself.
    Actually, no. You only need to change the system palette when you need to display or otherwise manipulate an 8-bit image(assuming an 8-bit display, of course) that doesn't use the currently selected system palette. If you are taking a screenshot of the current desktop/window then it must be using the currently selected system palette; if you haven't changed it then the default, system palette is there (if it wasn't you wouldn't see much on your screen to begin with).

    Since you are getting the correct screen image but the colours are wrong your issue most likely lies with how you are retrieving/storing the system palette.

    Which 256 colors should be in there, that I don't know...
    If all you need to do is retrieve the system palette then just use GetSystemPaletteEntries with the desktop device context as the dc in question.

    If that's still of no help it may be a good idea to post a minimal, compilable complete example that replicates the problem as that might better help us to help you.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  10. #10
    Registered User
    Join Date
    May 2007
    Posts
    6
    Quote Originally Posted by Ken Fitlike View Post
    If you are taking a screenshot of the current desktop/window then it must be using the currently selected system palette; if you haven't changed it then the default, system palette is there (if it wasn't you wouldn't see much on your screen to begin with).
    So, now I'm confused. If I understood correctly, you said that if I do not set any palette to my bitmap, then the dafault system palette will be there... but, if don't set any, then the final .bmp file is all black. That's the reason I posted that you need to specify which palette will be used to display the bmp, rather than Windows selecting its own.

    Here's a screenshot of what I get I do not fill the RGBQUAD struct in the HEADER (or if I don't fwrite it to the file): Black image

    Quote Originally Posted by Ken Fitlike View Post
    If all you need to do is retrieve the system palette then just use GetSystemPaletteEntries with the desktop device context as the dc in question.
    That's what I've been trying. I really don't know if I'm doing it correctly.

    Code:
    	LPLOGPALETTE lpLogPal = (LPLOGPALETTE) malloc(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY));
    		lpLogPal->palVersion    = 0x300;
    		lpLogPal->palNumEntries = 256;		
    		
    		HDC hdc = GetDC(NULL);
    		GetSystemPaletteEntries(hdc, 0, 256,
    			   (LPPALETTEENTRY)(lpLogPal->palPalEntry));
    		ReleaseDC(NULL, hdc);		
    
    		int i;
    
                                    /* First and last 10 entries are reserved by Windows 
                                        and should have 0 as their flag */
    		for (i = 0; i < 10; i++)
    			lpLogPal->palPalEntry[i].peFlags = 0;
    		for(i = 246; i < 256; i++)
    			lpLogPal->palPalEntry[i].peFlags = 0;	
    	
                                   /* PC_NOCOLLAPSE ensures that the palette manager
                                      allocates a free entry in the system palette if one is available
                                      and only uses the closest colour match if there are no 
                                      (more) free entries in the system palette.*/
    		for (i = 10; i < 246 ; i++)
    			lpLogPal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
    			
    		for (i = 0; i < 256; i++){	
    			pBitmapInfo->bmiColors[i].rgbBlue     = lpLogPal->palPalEntry[i].peBlue;
    			pBitmapInfo->bmiColors[i].rgbRed      = lpLogPal->palPalEntry[i].peRed;
    			pBitmapInfo->bmiColors[i].rgbGreen    = lpLogPal->palPalEntry[i].peGreen;
    			pBitmapInfo->bmiColors[i].rgbReserved = lpLogPal->palPalEntry[i].peFlags;
    		}
    There I copy the entire system palette to my bmiColor struct. If I do that, then the bmp isn't all black anymore but I'm still getting bad coloring. Here's a screenshot of the same picture shown above but with this struct fwrited on it:Image with RGBQUAD defined

    See the difference ? In that last image, Windows is using the palette I selected... which should be the very same default system palette. Maybe there's a problem with those 0 and NOCOLLAPSE flags...

    Any more ideas ? I know I have run out of them a while ago...

  11. #11
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    If I understood correctly
    I was referring to how the system displays the image; the palette is a separate entity from the image bits themselves (which are just indexes into that palette for an 8-bit image).
    I really don't know if I'm doing it correctly.
    I haven't tested it but it looks fine to me (but see below) except you should check the return values of these api functions to ensure they are functioning as you intend.

    It still looks pretty much as I described in my first post in this thread: you're filling the RGBQUADs the wrong way around - note that the PALLETEENTRY struct members are declared in the order red, green then blue while the RQBQUAD switches the red and the blue bytes. I still suspect the mismatch in observed image colours is because you have not taken this into account.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  12. #12
    Registered User
    Join Date
    May 2007
    Posts
    6
    Quote Originally Posted by Ken Fitlike View Post
    It still looks pretty much as I described in my first post in this thread: you're filling the RGBQUADs the wrong way around - note that the PALLETEENTRY struct members are declared in the order red, green then blue while the RQBQUAD switches the red and the blue bytes. I still suspect the mismatch in observed image colours is because you have not taken this into account.
    I really think that they are not in the wrong order. I tried this:
    Code:
    	for (i = 0; i < 256; i++){	
    			pBitmapInfo->bmiColors[i].rgbRed      = lpLogPal->palPalEntry[i].peBlue;
    			pBitmapInfo->bmiColors[i].rgbGreen    = lpLogPal->palPalEntry[i].peGreen;
    			pBitmapInfo->bmiColors[i].rgbBlue     = lpLogPal->palPalEntry[i].peRed;
    			pBitmapInfo->bmiColors[i].rgbReserved = lpLogPal->palPalEntry[i].peFlags;
    		}
    ...along with others combinations, and yet nothing worked (still getting messed up colors).

    One thing that I've come to notice is that the wallpaper in my desktop is displaying fine with my previously posted code (and not with this inverted assignations). However, the Start menu and all the icons (as well as any other window) are still bad.

    My guess is that the palette that GetSystemPalette() returns with the parameters I'm using, is just the one used to display the desktop and nothing else. If there is anything on top of it (i.e my VC++ window, as shown in the captures in previous posts) then it'll be displayed using that palette which may not have all the needed colors to do so correctly (but has the ones that correspond to my wallpaper). This is what I mean: Desktop image

    Except for the light green sky in the picture (which should be white/gray), the wallpaper is displaying perfectly.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Feedback: Functional Specification Wording
    By Ragsdale85 in forum C++ Programming
    Replies: 0
    Last Post: 01-18-2006, 04:56 PM
  2. Capturing dos text screen in C
    By hednast in forum C Programming
    Replies: 13
    Last Post: 08-11-2005, 07:49 PM
  3. char copy
    By variable in forum C Programming
    Replies: 8
    Last Post: 02-06-2005, 10:18 PM
  4. i am not able to figure ot the starting point of this
    By youngashish in forum C++ Programming
    Replies: 7
    Last Post: 10-07-2004, 02:41 AM
  5. screen capturing
    By Neuby_44 in forum C Programming
    Replies: 1
    Last Post: 02-10-2003, 04:06 AM