在win7下为opengl窗口捕获的图像是黑色的

Posted

技术标签:

【中文标题】在win7下为opengl窗口捕获的图像是黑色的【英文标题】:captured image for an opengl window is black under win7 【发布时间】:2015-04-01 20:42:53 【问题描述】:

我在主窗口上有几个子窗口,有的是GDI窗口,有的是opengl渲染的窗口,一个功能是用一个矩形来捕捉图像(可能覆盖不同的窗口组合)。此功能在 windows xp 下运行良好。但是,在 windows 7 下,所有 opengl 渲染的窗口都是黑色的。我做了一些研究,有人说gdi不能通过window DC直接访问帧缓冲区,必须使用glReadPixels来组合位图。然而,这种方法很尴尬,因为我必须分别组合该矩形中的每个窗口。有人对我有更好的选择吗?

这是我捕获 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);

opengl窗口配置为:

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
;  

我想再次强调一个事实: xp下可以,win7下不行(opengl窗口部分是黑色的)

【问题讨论】:

根据我的经验,这只是发生。有时,窗口在视频卡上的渲染和合成方式使得主处理器无法真正“看到”它。我知道当人们捕获游戏的屏幕截图或视频时,他们经常会因为这个问题而求助于专门的应用程序。捕获媒体播放器的屏幕截图时也会发生这种情况。它似乎因 Window 使用的操作系统、驱动程序和图形库而异。 是的。在任何规范或文档中都不能保证允许您通过 GDI 捕获 OpenGL 帧缓冲区的内容。它可能在某些情况下有效,但并非必须如此。实际上,它也不适用于 XP 的所有情况。 感谢 cmets,我想我必须使用其他程序来执行此操作,例如 ffmpeg。 【参考方案1】:

您好,我终于找到了一个完美的解决方案。根据 Mats Pertersson 提供的信息,我很确定这就是原因,因为它与事实相符。 Windows 7 引入了透明窗口外观,每个窗口都不是最终结果。最终结果(屏幕输出)由所有窗口组成。所以我来了解决方案,捕获最终屏幕而不是捕获主窗口。它在 xp 和 win 7 下都能完美运行。

主要变化:所有DC都来自屏幕而不是窗口,因此相关功能全部更改为全局gdi功能。

代码如下:

    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);

【讨论】:

【参考方案2】:

作为过去十年左右使用 GPU 开关(目前“打开”,但不做图形)的开发人员,我将尝试解释发生了什么:

GPU 通常有多个“层”可以输出到 - 例如,在现代显卡上,鼠标光标位于它自己的层中,因此我们不必重绘事物(如过去是这样,显卡/驱动程序会“记住”鼠标光标下的内容,以及移动鼠标时的重绘)。该层位于屏幕上实际图形的顶部,当帧缓冲存储器被扫描出时,它们被组合在一起——也就是说,当像素颜色被发送到显示器本身时——每一层都以定义的顺序读取,并且不同层的颜色根据它们各自的 alpha 值进行组合。

一些 OpenGL 驱动程序和硬件发现将 3D 绘制到一个单独的层要容易得多,然后在“扫描”阶段将两者结合起来。这有时会提供更好的性能,因为 GL 驱动程序“拥有”这一层,而不必与 GDI 同时尝试绘制到屏幕上。

当然,当 GDI 读回内容时,它只能读取 GDI 知道的内容[这也是为什么鼠标光标通常不会出现在屏幕副本中的原因]

【讨论】:

感谢您的信息。这真的很有帮助,并且清楚地解释了原因。有什么解决办法吗? 不是真的,除了识别 GL 窗口和使用 glReadPixels 读取之外,就是这样。如果目的只是为了获取特定应用程序的屏幕截图,则可以将显卡更改为更简单的显卡,甚至运行纯软件 OpenGL 驱动程序 - 但这可能会导致性能下降,我该如何放置很好......吸吮? ;)

以上是关于在win7下为opengl窗口捕获的图像是黑色的的主要内容,如果未能解决你的问题,请参考以下文章

在OpenGL中实现VBO,窗口保持黑色

OpenGL:如何设置 OpenGL 只渲染到 FBO,不输出到屏幕/窗口/控件?

在OpenGL中加载图像以用于GIS应用程序

Android opengl 画文字,怎么把文字后面的黑色背景去掉

亮度滤波器opengl中的多彩噪声

Qt5.5下捕获一个带有OpenGL内容的Widget