我今天写出了返回GDI+的Bitmap的屏幕捕捉方法,供需要者参考。(记得前几天看到有人问这个问题)代码如下:
enum ScreenType
{
FullScreen,
WorkArea,
ActiveWindow
};Bitmap* CaptureScreen(ScreenType type)
{
HWND wnd;
HDC hDC;
if (type == ActiveWindow) {
wnd = ::GetActiveWindow();
hDC = ::GetWindowDC(wnd); 
}
else {
wnd = ::GetDesktopWindow();
hDC = ::GetDC(NULL); 
} ATLASSERT(::IsWindow(wnd)); CRect rect; if(type==WorkArea )
{
::SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
}
else
{
::GetWindowRect(wnd, &rect);
} HDC hMemDC = ::CreateCompatibleDC(hDC); //most of the remaining code is normal GDI stuff
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, rect.Width(), rect.Height());
if (hBitmap)
{
HBITMAP hOld = (HBITMAP) ::SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, rect.Width(), rect.Height(), hDC, 0, 0, SRCCOPY);
SelectObject(hMemDC, hOld);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
} return Bitmap::FromHBITMAP(hBitmap, NULL);
}这个方法在大多数情况下是有效的。存在的问题:
如果屏幕上有半透明窗口,在取得Desktop的时候无法取到。半透明窗口就像完全透明了一下。而键盘上的「Print Screen」却可以正确取到。如果你有正确的办法请告知。

解决方案 »

  1.   

    谢谢指出BUG,我想应该主要是资源泄漏问题吧?修改代码如下,如仍有BUG,望明示Bitmap* CGdiPlusHelper::CaptureScreen(ScreenType type)
    {
    HWND wnd;
    HDC hDC;
    Bitmap* pBitmap = NULL; if (type == ActiveWindow) {
    wnd = ::GetActiveWindow();
    hDC = ::GetWindowDC(wnd);
    }
    else {
    wnd = ::GetDesktopWindow();
    hDC = ::GetDC(NULL);
    } ATLASSERT(::IsWindow(wnd)); CRect rect; if(type==WorkArea )
    {
    ::SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
    }
    else
    {
    ::GetWindowRect(wnd, &rect);
    } //most of the remaining code is normal GDI stuff
    HDC hMemDC = ::CreateCompatibleDC(hDC);
    HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, rect.Width(), rect.Height()); if (hBitmap)
    {
    HBITMAP hOld = (HBITMAP) ::SelectObject(hMemDC, hBitmap);
    BitBlt(hMemDC, 0, 0, rect.Width(), rect.Height(), hDC, 0, 0, SRCCOPY);
    SelectObject(hMemDC, hOld);
    //Create GDI+ Bitmap from HBITMAP
    pBitmap = Bitmap::FromHBITMAP(hBitmap, NULL);
    pBitmap = Bitmap::FromHBITMAP(hBitmap, NULL); DeleteObject(hBitmap);
    } DeleteDC(hMemDC);
    ReleaseDC(wnd,hDC); return pBitmap;
    }
      

  2.   

    Bugs and issues1) GetDC(NULL) does not match with ReleaseDC(hwnd, hDC).2) CreateCompatibleBitmap should be avoid. It will create 256-color bitmap under 8-bit screen display mode. You need a palette for it to be displayed properly. Create a 24-bpp DIB section or 32-bpp DIB section for always getting good result.3) Bitmap::FromHBITMAP is called twice. Typo?4) Are you sure you can call DeleteObject(hBitmap)? Double check GDI+ documentation. Add a comment if it's safe to delete the HBITMAP.5) Design issue: seperate into two functions, first capture window into HBITMAP, and then the second one convert HBITMAP into GDI+ bitmap, for better reusability.
      

  3.   

    谢谢yuanfeng,1) GetDC(NULL) does not match with ReleaseDC(hwnd, hDC).如果GetDC(NULL)不应该对应ReleaseDC(hwnd, hDC)的话,那么MFC/WTL的CWindowDC类就存在BUG,因为他们都没有考虑hwnd==NULL的情况。2) CreateCompatibleBitmap should be avoid. It will create 256-color bitmap under 8-bit screen display mode. You need a palette for it to be displayed properly. Create a 24-bpp DIB section or 32-bpp DIB section for always getting good result.
    谢谢你教会我这么重要的知识。我尝试了去生成DIB,发现完美的实现还是需要做好多工作的。我在后面的实现里使用了CImage封装类来帮助我作这个工作。3) Bitmap::FromHBITMAP is called twice. Typo?
    这是贴代码是时的失误。4) Are you sure you can call DeleteObject(hBitmap)? Double check GDI+ documentation. Add a comment if it's safe to delete the HBITMAP.
    Thanks again。确是如你所说,我做了危险的工作。在后面的代码里我使用了Clone来解决这个问题。5) Design issue: seperate into two functions, first capture window into HBITMAP, and then the second one convert HBITMAP into GDI+ bitmap, for better reusability.
    因为必须考虑资源泄漏等问题,如果我分成不同的函数,那么何时进行资源释放必须谨慎小心。我还是考虑相对安全的实现,返回Bitmap指针。下面是我修改过的代码,如有问题请指正,谢谢
    Bitmap* CGdiPlusHelper::CaptureScreen(ScreenType type)
    {
    HWND wnd;
    if (type == ActiveWindow) {
    wnd = ::GetActiveWindow();
    }
    else {
    wnd = ::GetDesktopWindow();
    } ATLASSERT(::IsWindow(wnd)); CRect rect;
    if(type==WorkArea )
    {
    ::SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
    }
    else
    {
    ::GetWindowRect(wnd, &rect);
    } CWindowDC  winDC(NULL);
    CImage image; int nBPP = winDC.GetDeviceCaps(BITSPIXEL) * winDC.GetDeviceCaps(PLANES);
    if (nBPP < 24)
    nBPP = 24; BOOL bStat = image.Create(rect.Width(), rect.Height(), nBPP);
    ATLASSERT(bStat); if (!bStat)
    return NULL; CImageDC imageDC(image);
    ::BitBlt(imageDC, 0, 0, rect.Width(), rect.Height(), winDC, 0, 0, SRCCOPY); Bitmap* pBitmap;
    {
    Bitmap bmpTemp((HBITMAP)image, NULL);
    pBitmap = bmpTemp.Clone(0, 0, rect.Width(), rect.Height(), PixelFormat32bppARGB);
    } return pBitmap;
    }
      

  4.   

    另外如果desktop上有半透明的窗口,怎样才能像PrintScreen一样正确捕捉到,仍然是技术难题。
    难道::GetDC(NULL); 取到的DC里面就没有半透明的图像?
    还是根本就不能用GDI方法呢?
    或者BitBlt无法正确拷贝半透明图像????
      

  5.   

    发现有一行贴错了
    错:CWindowDC  winDC(NULL);
    正:CWindowDC  winDC(wnd);
      

  6.   

    对于关透明的问题,试试用下面方法得到屏幕DCHDC hDC = ::CreateDC("DISPLAY", NULL, NULL, NULL);
      

  7.   

    HDC hDC = ::CreateDC("DISPLAY", NULL, NULL, NULL);
    无效。无法解决带有透明窗口的Desktop问题。
      

  8.   

    经工具检测我上面写的代码仍然存在错误,主要是CImage析构的时机问题。
    代码重新修改如下,注意多层大括号的使用技巧,它影响了自动变量的生存期间。Bitmap* CGdiPlusHelper::CaptureScreen(ScreenType type)
    {
    HWND wnd;
    if (type == ActiveWindow) {
    wnd = ::GetActiveWindow();
    }
    else {
    wnd = ::GetDesktopWindow();
    } ATLASSERT(::IsWindow(wnd)); CRect rect;
    if(type==WorkArea )
    {
    ::SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
    }
    else
    {
    ::GetWindowRect(wnd, &rect);
    } CWindowDC  winDC(wnd);
    CImage image; int nBPP;
    nBPP = winDC.GetDeviceCaps(BITSPIXEL) * winDC.GetDeviceCaps(PLANES);
    if (nBPP < 24)
    nBPP = 24; BOOL bStat = image.Create(rect.Width(), rect.Height(), nBPP);
    ATLASSERT(bStat); if (!bStat)
    return NULL; Bitmap* pBitmap = NULL;
    {
    CImageDC imageDC(image);
    ::BitBlt(imageDC, 0, 0, rect.Width(), rect.Height(), winDC, rect.left, rect.top, SRCCOPY);
    {
    Bitmap bmpTemp((HBITMAP)image, NULL);
    pBitmap = bmpTemp.Clone(0, 0, rect.Width(), rect.Height(), PixelFormat32bppARGB);
    if (bmpTemp.GetLastStatus() != Gdiplus::Ok)
    {
    pBitmap = NULL;
    }
    }
    } return pBitmap;
    }取得带有透明窗口的Desktop另有办法解决,上面的方法仍然无法解决这个问题。
      

  9.   

    不知道有谁有directdraw捕捉屏幕的代码呀?拿出来分享一下呀!