2
votes

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)

2
In my experience, this just happens. Sometimes the window was rendered and composited on the video card in such a way that the host processor can't really "see" it. I know that when people capture screenshots or videos of games they often resort to specialized apps because of this problem. This also happens when capturing screen shots of media players. It seems to vary based on the OS, driver, and graphics library the Window is using.Moby Disk
Yes. There is no guarantee anythere in any specifiaction or document which would allow you to capture the contents of an OpenGL framebuffer via GDI. It mght work in some circumstances, but it doesn't have to. And actually, it doesn't work in all cases on XP either.derhass
Thanks for the comments, I guess I have to use some other program to do this, such as ffmpeg.shangping

2 Answers

2
votes

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);
}
0
votes

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]