StretchDIBits failing only at specific image dimensions - c++

I'm using StretchDIBits to print an image and it is failing when the image is at certain sizes for some unknown reason.
The image data is loaded into an unsigned int array from some some other image source in 24-bit BGR format. I've already verified that the image and buffer are perfectly fine since, like I said, it works at some sizes but not at all.
The current size I'm testing with is 638x1014. If I change the height to 1013 it works fine, but for some reason it just flat out fails if it's 1014.
Here's some code to show you how it's all being setup:
unsigned int * buffer = new unsigned int[width * height * 3];
// Fill buffer with image data...
BITMAPINFOHEADER bi = { 0 };
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biSizeImage = width * height * 3; // Specifying this value because if I don't it will crash trying to read outside of the buffer.
StartPage(hdcPrint);
SetMapMode(hdcPrint, MM_ISOTROPIC);
SetWindowExtEx(hdcPrint, width, height, NULL);
SetViewportExtEx(hdcPrint, width, height, NULL);
SetViewportOrgEx(hdcPrint, 0, 0, NULL);
StretchDIBits(hdcPrint, 0, 0, width, width, 0, 0, width, height, buffer, (BITMAPINFO *) &bi, DIB_RGB_COLORS, SRCCOPY);
StretchDIBits returns zero when it fails and the print result is a blank page.
I have a vague idea of what the problem is because, like it says in the comment, if I don't specify biSizeImage and leave it at zero then StretchDIBits will cause a crash because it tries to read past the end of the buffer. Even so, I have no idea how to diagnose exactly why it's doing that since it works at some sizes but not others.

Your width is the wrong number of bytes. Windows requires that each line be a multiple of 4 bytes; 638*3 is 1914, which is 2 bytes shy.

Related

captured image for an opengl window is black under win7

I have several child window on the main window, and some are GDI windows, and some are opengl rendered window, one function is to capture the image with a rect (may cover different combination of windows). This function works fine under windows xp. However, under windows 7, all opengl rendered windows are black. I did some research and someone said that the gdi cannot directly access the frame buffer via the window DC, and has to use glReadPixels to combine the bitmap. This approach however is awkward since I have to combine each window in that rect separately. Anyone has a better option for me?
Here is my code for catching a bmp:
void MainWndClass::catchBmp(const char* path_fn, bool drawAreaOnly /*=0*/)
{
CDC *pDC=GetDC();
int BitPerPixel = pDC->GetDeviceCaps(BITSPIXEL);
int Left,Top,Width,Height;
if (drawAreaOnly)
{
Left = rBDWin.left;
Top = rBDWin.top;
Width = rBDWin.right-rBDWin.left;
Width = Width/4*4;
Height = rBDWin.bottom-rBDWin.top;
Height = Height/4*4;
}
else
{
Left=rbmpWin.left;
Top=rbmpWin.top;
Width=rbmpWin.right-rbmpWin.left;
Width=Width/4*4;
Height=rbmpWin.bottom-rbmpWin.top;
Height=Height/4*4;
}
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap memBitmap, *oldmemBitmap;
memBitmap.CreateCompatibleBitmap(pDC, Width, Height);
//it seems does no work
//short bpp=24;
if(BitPerPixel>24) BitPerPixel=24;
memBitmap.SetBitmapBits(2,&BitPerPixel);
oldmemBitmap = memDC.SelectObject(&memBitmap);
//copy the bitmap from the pDC (source)
memDC.BitBlt(0, 0, Width, Height, pDC, Left, Top, SRCCOPY);
/*
CString title;
GetWindowText(title);
memDC.SetBkMode(TRANSPARENT);
memDC.TextOut(64,4,title);
*/
BITMAP bmp;
memBitmap.GetBitmap(&bmp);
if(bmp.bmBitsPixel>24)
{
bmp.bmBitsPixel=24;
//bmp.bmWidthBytes=bmp.bmWidth*3;
}
bmp.bmWidthBytes=bmp.bmWidth*(bmp.bmBitsPixel/8);
FILE *fp=NULL;
//path_fn+=".bmp";
fp=fopen((LPCTSTR)path_fn,"w+b");
BITMAPINFOHEADER bih = {0};
bih.biBitCount = bmp.bmBitsPixel;
bih.biCompression = BI_RGB;
bih.biHeight = bmp.bmHeight;
bih.biPlanes = 1;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biSizeImage = bmp.bmWidthBytes * bmp.bmHeight;
bih.biWidth = bmp.bmWidth;
BITMAPFILEHEADER bfh = {0};
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfSize = bfh.bfOffBits + bmp.bmWidthBytes * bmp.bmHeight;
bfh.bfType = (WORD)0x4d42;
if(fp)
{
fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
}
byte * p = new byte[bmp.bmWidthBytes * bmp.bmHeight];
//copy the bits to the buffer
int ret=GetDIBits(memDC.m_hDC, (HBITMAP) memBitmap.m_hObject, 0, Height, p,
(LPBITMAPINFO) &bih, DIB_RGB_COLORS);
if(fp)
fwrite(p, 1, bmp.bmWidthBytes * bmp.bmHeight, fp);
delete [] p;
if(fp)
fclose(fp);
memDC.SelectObject(oldmemBitmap);
}
The opengl window is configured as:
PIXELFORMATDESCRIPTOR pixelDesc =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0,0,0,0,0,0,
0,
0,
0,
0,0,0,0,
32,//
0,
0,
PFD_MAIN_PLANE,
0,
0,0,0
};
I want to emphysis the fact again:
it works under xp, but not under win7 (the opengl window part is black)
Hello I finally got a perfect solution for this. According to information given by Mats Pertersson, and I am pretty sure that is the reason since it matches the facts. Windows 7 introduces the transparent window appearance, and each window is not the final results. Final results (the screen outputs) are composed from all the windows. So I came the solution, capture the final screen instead of capture the main window. And it works perfect under both xp and win 7.
Main changes: all DC comes from the screen instead of the window, hence relating functions are all changed to global gdi functions.
Here is the code:
catchBmp(const char* path_fn, bool drawAreaOnly /*=0*/)
{
//CDC *pDC=GetDC();
HDC hdcScreen;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
hdcScreen=::GetDC(NULL);
int BitPerPixel = ::GetDeviceCaps(hdcScreen,BITSPIXEL);
int Left,Top,Width,Height;
if (drawAreaOnly)
{
Left = rBDWin.left;
Top = rBDWin.top;
Width = rBDWin.right-rBDWin.left;
Width = Width/4*4;
Height = rBDWin.bottom-rBDWin.top;
Height = Height/4*4;
}
else
{
Left=rbmpWin.left;
Top=rbmpWin.top;
Width=rbmpWin.right-rbmpWin.left;
Width=Width/4*4;
Height=rbmpWin.bottom-rbmpWin.top;
Height=Height/4*4;
}
hdcMemDC=::CreateCompatibleDC(hdcScreen);
hbmScreen=::CreateCompatibleBitmap(hdcScreen,Width,Height);
if(BitPerPixel>24) BitPerPixel=24;
::SetBitmapBits(hbmScreen,2,&BitPerPixel);
::SelectObject(hdcMemDC,hbmScreen);
BitBlt(hdcMemDC,
0,0,Width,Height,hdcScreen,Left,Top,SRCCOPY);
::GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);
if(bmpScreen.bmBitsPixel>24)
{
bmpScreen.bmBitsPixel=24;
}
bmpScreen.bmWidthBytes=bmpScreen.bmWidth*(bmpScreen.bmBitsPixel/8);
FILE *fp=NULL;
fp=fopen((LPCTSTR)path_fn,"w+b");
BITMAPINFOHEADER bih = {0};
bih.biBitCount = bmpScreen.bmBitsPixel;
bih.biCompression = BI_RGB;
bih.biHeight = bmpScreen.bmHeight;
bih.biPlanes = 1;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biSizeImage = bmpScreen.bmWidthBytes * bmpScreen.bmHeight;
bih.biWidth = bmpScreen.bmWidth;
BITMAPFILEHEADER bfh = {0};
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfSize = bfh.bfOffBits + bmpScreen.bmWidthBytes * bmpScreen.bmHeight;
bfh.bfType = (WORD)0x4d42;
if(fp)
{
fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
}
byte * p = new byte[bmpScreen.bmWidthBytes * bmpScreen.bmHeight];
GetDIBits(hdcScreen, hbmScreen, 0, Height, p, (LPBITMAPINFO) &bih, DIB_RGB_COLORS);
if(fp)
fwrite(p, 1, bmpScreen.bmWidthBytes * bmpScreen.bmHeight, fp);
delete [] p;
if(fp)
fclose(fp);
::DeleteObject(hbmScreen);
::DeleteObject(hdcMemDC);
::ReleaseDC(NULL,hdcScreen);
//memDC.SelectObject(oldmemBitmap);
}
As a developer working with GPU's on and off (currently "on", but not doing graphigs) for the past ten or so years, I'll try to explain what is going on:
The GPU often have more than one "layer" that it can output to - for example, on modern graphics cards, the mouse cursor lives in a layer of it's own, so that we don't have to redraw things (as used to be the case, the video card/driver would "remember" what was under the mouse-cursor, and the redraw that when you moved the mouse). That layer is on top of the actual graphics on the screen, and they are combined when the frame buffer memory is scanned out - that is, when pixel-colours are sent to the display itself - each layer is read in a defined order, and the colour of the different layers are combined according to their respective alpha-value.
Some OpenGL drivers & hardware find it much easier to draw the 3D to a separate layer, and then combine the two during "scanning out" phase. This sometimes gives better performance, because the GL driver "owns" this layer, and doesn't have to fight with GDI trying to draw to the screen at the same time.
Of course, when GDI reads back the content, it can only read the content that GDI knows about [this is also why the mouse cursor is typically not present in a screen-copy]

Editing bitmap in memory using C++

I have a two dimensional array of data that I want to display as an image.
The plan goes something like this -
Create a bitmap using CreateCompatibleBitmap (this results in a solid black bitmap and I can display this with no problems)
Edit the pixels of this bitmap to match my data
BitBlt the bitmap to the window
I think that I need a pointer to the place in memory where the pixel data begins. I've tried many different methods of doing this and googled it for 3 days and still haven't been able to even edit a single pixel.
Using a loop of SetPixel(HDC, x, y, Color) to set each pixel works but VERY slowly.
I have accomplished this in C# by locking the bitmap and editing the bits, but I am new to C++ and can't seem to figure out how to do something similar.
I have mostly been trying to use memset(p, value, length)
For "p" I have tried using the handle returned from CreateCompatibleBitmap, the DC for the bitmap, and the DC for the window. I have tried all sorts of values for the value and length.
I'm not sure if this is the right thing to use though.
I don't have to use a bitmap, that's just the only thing I know to do. Actually it would be awesome to find a way to directly change the main window's DC.
I do want to avoid libraries though. I am doing this purely for learning C++.
This took QUITE a bit of research so I'll post exactly how it is done for anyone else who may be looking.
This colors every pixel red.
hDC = BeginPaint(hWnd, &Ps);
const int
width = 400,
height = 400,
size = width * height * 3;
byte * data;
data = new byte[size];
for (int i = 0; i < size; i += 3)
{
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 255;
}
BITMAPINFOHEADER bmih;
bmih.biBitCount = 24;
bmih.biClrImportant = 0;
bmih.biClrUsed = 0;
bmih.biCompression = BI_RGB;
bmih.biWidth = width;
bmih.biHeight = height;
bmih.biPlanes = 1;
bmih.biSize = 40;
bmih.biSizeImage = size;
BITMAPINFO bmpi;
bmpi.bmiHeader = bmih;
SetDIBitsToDevice(hDC, 0, 0, width, height, 0, 0, 0, height, data, &bmpi, DIB_RGB_COLORS);
delete[] data;
memset can be used on the actually RGB information array (but you need to also know the format of the bitmap, if a pixel has 32 or 24 bits ).
From a bit of research on msdn, it seems that what you want to get is the BITMAP structure :
http://msdn.microsoft.com/en-us/library/k1sf4cx2.aspx
There you have the bmBits on which you can memset.
How to get there from your function ?
Well, CreateCompatibleBitmap returns a HBITMAP structure and it seems you can get BITMAP from HBITMAP with the following code :
BITMAP bmp;
GetObject(hBmp, sizeof(BITMAP), &bmp);
This however seems to get a you copy of the existing bitmap info, which only solves your memset problem (you can now set the bitmap information with memset, eventhou I don't see any other use for memeset besides making the bmp all white or black).
There should be a function that allows you to set the DC bites to a bitmap thou, so you should be able to use the new bitmap as a parameter.

OpenGL game screen capture

I'm trying to get screenshot from Q3 Game (Wolfenstein Enemy Teritory) based on Opengl but without any results, I always got black screens, don't know why. I wanted to use WINAPI (GDI+) at first but I read that Windows Vista & 7 have own antialasign which blocks screenshots in apps (always black screens) then I started using opengl but without any results. These references which I based on:
testMemIO &
How to take screenshot in opengl
typedef void (WINAPI qglReadPixels_t)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
typedef void (WINAPI qglReadBuffer_t)(GLenum mode);
qglReadPixels_t *qaglReadPixels;
qglReadBuffer_t *qaglReadBuffer;
void GetScreenData()
{
// Initialize FreeImage library
FreeImage_Initialise(false);
FIBITMAP *image2, *image1;
DWORD ImageSize = 0;
TCPSocketConnection FileServer;
EndPoint ServerAddress;
screen_struct ss_data;
int Width = 1366;
int Height = 768;
BYTE *pixels = new BYTE[3 * Width * Height];
BYTE *Data = NULL;
DWORD Size = 0;
FIMEMORY *memstream = FreeImage_OpenMemory();
HMODULE OpenGL = GetModuleHandle("opengl32");
qaglReadPixels = (qglReadPixels_t *)GetProcAddress(OpenGL, "glReadPixels");
qaglReadBuffer = (qglReadBuffer_t *)GetProcAddress(OpenGL, "glReadBuffer");
qaglReadBuffer(GL_BACK);
qaglReadPixels(0, 0, Width, Height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
// Convert raw data into jpeg by FreeImage library
image1 = FreeImage_ConvertFromRawBits(pixels, Width, Height, 3 * Width, 24, 0x0000FF, 0xFF0000, 0x00FF00, false);
image2 = FreeImage_ConvertTo24Bits(image1);
// retrive image data
FreeImage_SaveToMemory(FIF_JPEG, image2, memstream, JPEG_QUALITYNORMAL);
FreeImage_AcquireMemory(memstream, &Data, &Size);
memset(&ss_data, 0x0, sizeof(screen_struct));
ss_data.size = size;
// Send image size to server
FileServer.Connect(Server->GetAddress(), 30003);
// Send entire image
FileServer.Send((char *)&ss_data, sizeof(screen_struct));
FileServer.SendAll((char *)Data, Size);
FileServer.Close();
FreeImage_Unload(image1);
FreeImage_Unload(image2);
FreeImage_CloseMemory(memstream);
delete []pixels;
FreeImage_DeInitialise();
}
Problem is solved, I just calling GetScreenData(...) before SwapBuffers(...) now it works correctly but there is still a weird thing, on some computers I'v got shifted screens, for example: Screen #1 Don't know why it happens, for sure it happens on Nvidia 5xxx(m) i 7xxx(m) series so far as I know.
Big thanks for #AndonM.Coleman

Creating 8bpp bitmap with GDI and saving it as a file

I have a perfectly working code that creates 32bpp bitmap and I need to change it so that 8bpp bitmap is created.
Here's the piece of code that creates 32bpp bitmap, draws into it, then it creates a bitmap file and store it into the vector of bytes:
// prepare bitmap:
BYTE* bitmap_data = NULL;
HDC hDC = GetDC(NULL);
HDC memHDC = CreateCompatibleDC(hDC);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = desiredWidth; // desiredWidth is 800
bmi.bmiHeader.biHeight = desiredHeight; // desiredHeight is 202
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = (((desiredWidth * bmi.bmiHeader.biBitCount + 31) & ~31) >> 3) * desiredHeight;
HBITMAP bitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&bitmap_data, NULL, NULL);
ReleaseDC(NULL, hDC);
DeleteDC(hDC);
... // drawing into bitmap
// prepare bitmap file header:
BITMAPFILEHEADER bf;
memset(&bf, 0, sizeof(BITMAPFILEHEADER));
bf.bfType = MAKEWORD('B', 'M');
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
bf.bfSize = bf.bfOffBits + bmi.bmiHeader.biSizeImage;
// write bitmap file into the vector:
std::vector<BYTE> bitmapData;
bitmapData.insert(bitmapData.end(), (BYTE*)&bf, ((BYTE*)&bf) + sizeof(BITMAPFILEHEADER));
bitmapData.insert(bitmapData.end(), (BYTE*)&bmi.bmiHeader, ((BYTE*)&bmi.bmiHeader) + sizeof(BITMAPINFOHEADER));
bitmapData.insert(bitmapData.end(), bitmap_data, bitmap_data + bmi.bmiHeader.biSizeImage);
And later the vector is stored into the file:
std::ofstream of("picture.bmp", std::ofstream::out | std::ofstream::binary);
of.write((char*)&bitmapData[0], bitmapData.size());
of.close();
and here's the output image:
What I've tried:
First step was naturally replacing 32 with 8 in this line: bmi.bmiHeader.biBitCount = 32; which resulted into image filled with solid grey colour. Then based on this answer I made following changes:
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
changed into:
struct BITMAPINFO256 {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[256];
} bmi;
memset(&bmi, 0, sizeof(BITMAPINFO256));
added this loop right before CreateDIBSection is called:
for (UINT i = 0; i < 256; i++) {
bmi.bmiColors[i].rgbRed = i;
bmi.bmiColors[i].rgbGreen = i;
bmi.bmiColors[i].rgbBlue = i;
}
and when the bmi.bmiHeader is being written into the vector, the RGBQUAD array is included: so sizeof(BITMAPINFO256) expresses the size of the header.
The new code (full code here) produces this output:
Why the new image looks that way? What's going on there? What am I missing?
Any help will be appreciated.
It looks like an alignment problem. Make sure you update bfOffBits in the BITMAPFILEHEADER so that it points to the first byte of the pixel data. (If you don't change it, then it probably points to the beginning of the palette.)
In other words, sizeof(RGBQUAD)*256 should be added here as well:
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
Also makes sure the first scanline starts on a DWORD boundary. That is, its offset from the beginning of the file should be a multiple of four bytes. Likewise, each scanline should be padded out to a multiple of four bytes. (You may not see these problems if your widths are nice even numbers. It's good to have an odd-width image among your test cases.)
You need to specify the size of the palette that you attached. Right now it's zero, so the palette is showing up as the first bunch of pixels in your image.
bmi.bmiHeader.biClrUsed = 256;
You need to generate a palette for your image.
Each pixel in a 32bit image is stored as 8-bits for Alpha, Red, Green and Blue.
Where as in a 8bit image, the value in each pixel an 8bit index into the palette.
Your for(i=0..255) { bmi.bmiColors[i].rgbRed = i; ....} code is generated an grey-scale palette.
If the whole image is coming out as grey then it sounds like an alignment error, from memory the width of a palettized image must be a multiple of 4.
Try saving a SMALL 256 colour (aka 8-bit image) from Paint and compare in a hex editor.

How to access pixel color within a bitmap?

I searched and I understood I'll have to use GetDIBits(). I don't know what to do with the LPVOID lpvBits out parameter.
Could someone please explain it to me? I need to get the pixel color information in a two dimensional matrix form so that I can retrieve the information for a particular (x,y) coordinate pair.
I am programming in C++ using Win32 API.
first you need a bitmap and open it
HBITMAP hBmp = (HBITMAP) LoadImage(GetModuleHandle(NULL), _T("test.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if(!hBmp) // failed to load bitmap
return false;
//getting the size of the picture
BITMAP bm;
GetObject(hBmp, sizeof(bm), &bm);
int width(bm.bmWidth),
height(bm.bmHeight);
//creating a bitmapheader for getting the dibits
BITMAPINFOHEADER bminfoheader;
::ZeroMemory(&bminfoheader, sizeof(BITMAPINFOHEADER));
bminfoheader.biSize = sizeof(BITMAPINFOHEADER);
bminfoheader.biWidth = width;
bminfoheader.biHeight = -height;
bminfoheader.biPlanes = 1;
bminfoheader.biBitCount = 32;
bminfoheader.biCompression = BI_RGB;
bminfoheader.biSizeImage = width * 4 * height;
bminfoheader.biClrUsed = 0;
bminfoheader.biClrImportant = 0;
//create a buffer and let the GetDIBits fill in the buffer
unsigned char* pPixels = new unsigned char[(width * 4 * height)];
if( !GetDIBits(CreateCompatibleDC(0), hBmp, 0, height, pPixels, (BITMAPINFO*) &bminfoheader, DIB_RGB_COLORS)) // load pixel info
{
//return if fails but first delete the resources
DeleteObject(hBmp);
delete [] pPixels; // delete the array of objects
return false;
}
int x, y; // fill the x and y coordinate
unsigned char r = pPixels[(width*y+x) * 4 + 2];
unsigned char g = pPixels[(width*y+x) * 4 + 1];
unsigned char b = pPixels[(width*y+x) * 4 + 0];
//clean up the bitmap and buffer unless you still need it
DeleteObject(hBmp);
delete [] pPixels; // delete the array of objects
so in short, the lpvBits out parameter is the pointer to the pixels
but if it is only 1 pixel you need i suggest to use getpixel to
I'm not sure if this is what you're looking for but GetPixel does pretty much what you need ...at least from i can tell from the function's description