标签归档:截屏

Capturing the Desktop Screen with the Mouse Cursor Image

下面的文章介绍了两种截屏方式及如何“截取”光标的方法。

通常情况下是无法截取光标的,因为光标不属于窗口中的windows元素,而是独立与窗口系统的另一层东西,叫Hot Spot。

申明:本文以下内容归原作者所有!

By 27 Jan 2006

DesktopCaptureWithMouse

Introduction

This article shows how you can capture screen images including the mouse cursor.

Background

Screen capturing is a very useful way of resource sharing as used in applications like Remote Desktop, Virtual Network Computing (VNC), where a user can access, view, and interact with a remote desktop as his own desktop. Also, it is used in non ethical applications like hacking applications, where a hacker can hack a computer using some malicious server application, and the server then frequently takes screenshots of the prey machine and sends them to the clients. You will see a lot of source code resources over the internet discussing how to take screenshots of the desktop or an area of the desktop but none of them discuss how to capture the mouse cursor bitmap with the screenshot. Sometimes it becomes necessary to capture the mouse to see the whole activity of the hacked machine. First, we will discuss here what the actual problem is.

Problem

Most of us think that the mouse cursor image is a part of the desktop display but actually it works on an upper layer over the desktop. Windows always tracks mouse with a “Hot Spot”, the actual position seen by the Windows.

Currently, there are two common ways to capture and manipulate the desktop image:

  1. Copy the desktop bitmap data from Video Memory to the System Memory. Do processing and then again blit it back to the Video Memory. This can be easily done using the BitBlt() or the StretchBlt() APIs provided by Win32.
  2. Another way is to directly manipulate the desktop bitmap in the Video Memory if enough memory is available as provided by DirectDraw.

Both of these don’t provide us the facility to capture the mouse cursor image with the desktop image.

Solution

The solution to the problem of capturing the mouse cursor image with the desktop image is quite simple.

  1. First, get the bitmap of the screen using BitBlt(). I have provided a simple function namedCaptureDesktop() in the CaptureScreen.cs file that captures the screen bitmap as almost all the codes available over the internet do.
  2. Then capture the mouse cursor bitmap as:

    Get Cursor Icon:

    First, get the cursor information using the Win32 GetCursorInfo(). The function fills the CURSORINFOstructure provided as a parameter. Don’t forget to initialize the cbSize member of the structure before passing it as an argument. Then we check whether the cursor is visible or not, by checking for the equality of the flagsmember of the filled structure with the CURSOR_SHOWING constant. If they are equal then we get the handle to the cursor icon using the CopyIcon() function that takes the hCursor member of the above filled structure.

    Get Cursor Position:

    Now, we have to get the Icon information so that we can get the hotspot position. This information is easily retrieved using the GetIconInfo() function. Here is the C# implementation of the mouse capturing function:

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
       Bitmap bmp;
       IntPtr hicon;
       Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO(); 
       Win32Stuff.ICONINFO icInfo;
       ci.cbSize = Marshal.SizeOf(ci);
       if(Win32Stuff.GetCursorInfo(out ci))
       {
           if (ci.flags == Win32Stuff.CURSOR_SHOWING)
           { 
               hicon = Win32Stuff.CopyIcon(ci.hCursor);
               if(Win32Stuff.GetIconInfo(hicon, out icInfo))
               {
                   x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                   y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
                   Icon ic = Icon.FromHandle(hicon);
                   bmp = ic.ToBitmap(); 
    
                   return bmp;
               }
           }
       }
       return null;
    }
  3. We now have both the bitmaps, i.e., the desktop bitmap and the mouse cursor bitmap with its position on the screen. Now, it’s time to place the mouse cursor bitmap on the desktop bitmap. I have provided the following function that places the mouse cursor image over the desktop bitmap at the proper position:
    public static Bitmap CaptureDesktopWithCursor()
    {     
       int cursorX = 0;
       int cursorY = 0;
       Bitmap desktopBMP;
       Bitmap cursorBMP;
       Bitmap finalBMP;
       Graphics g;
       Rectangle r;
       desktopBMP = CaptureDesktop();
       cursorBMP = CaptureCursor(ref cursorX, ref cursorY);
       if(desktopBMP != null)
       {
           if (cursorBMP != null)
           {
               r = new Rectangle(cursorX, cursorY, 
                       cursorBMP.Width, cursorBMP.Height);
               g = Graphics.FromImage(desktopBMP);
               g.DrawImage(cursorBMP, r);
               g.Flush();
               return desktopBMP;
           }
           else
               return desktopBMP;
       }
       return null;
    }
  4. The bitmap with the cursor is now ready to be rendered over a viewer surface (a PictureBox used here). Since the viewer’s visible area is smaller than the desktop bitmap area, scaling has been used in the function that displays the cooked desktop image.
    // ssWithMouseViewer is the PictureBox control
    private void Display(Bitmap desktop)
    {
        Graphics g;
        Rectangle r;
        if(desktop != null)
        {
            r = new Rectangle(0,0,ssWithMouseViewer.Width, 
                                ssWithMouseViewer.Height);
            g = ssWithMouseViewer.CreateGraphics();
            g.DrawImage(desktop,r);
            g.Flush();
        }
    }

Note

The binary image provided is compiled with Visual Studio .NET 2005 so you have to install .NET Framework 2.0.

Known problems

I have tested the application on my machine with no known problems, so I expect the same behavior on your machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

本文转自:http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor

三种截取屏幕的方式

Various methods for capturing the screen

By 19 Sep 2006

Contents

Introduction

Some times, we want to capture the contents of the entire screen programmatically. The following explains how it can be done. Typically, the immediate options we have, among others, are using GDI and/or DirectX. Another option that is worth considering is Windows Media API. Here, we would consider each of them and see how they can be used for our purpose. In each of these approaches, once we get the screenshot into our application defined memory or bitmap, we can use it in generating a movie. Refer to the article Create Movie From HBitmap for more details about creating movies from bitmap sequences programmatically.

Capture it the GDI way

When performance is not an issue and when all that we want is just a snapshot of the desktop, we can consider the GDI option. This mechanism is based on the simple principle that the desktop is also a window – that is it has a window Handle (HWND) and a device context (DC). If we can get the device context of the desktop to be captured, we can just blit those contents to our application defined device context in the normal way. And getting the device context of the desktop is pretty straightforward if we know its window handle – which can be achieved through the function GetDesktopWindow(). Thus, the steps involved are:

  1. Acquire the Desktop window handle using the function GetDesktopWindow();
  2. Get the DC of the desktop window using the function GetDC();
  3. Create a compatible DC for the Desktop DC and a compatible bitmap to select into that compatible DC. These can be done using CreateCompatibleDC() and CreateCompatibleBitmap(); selecting the bitmap into our DC can be done with SelectObject();
  4. Whenever you are ready to capture the screen, just blit the contents of the Desktop DC into the created compatible DC – that’s all – you are done. The compatible bitmap we created now contains the contents of the screen at the moment of the capture.
  5. Do not forget to release the objects when you are done. Memory is precious (for the other applications).

Example

Void CaptureScreen()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HWND hDesktopWnd = GetDesktopWindow();
    HDC hDesktopDC = GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC,hCaptureBitmap); 
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 
    SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
                                //here to save the captured image to disk
    ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

In the above code snippet, the function GetSystemMetrics() returns the screen width when used withSM_CXSCREEN, and returns the screen height when called with SM_CYSCREEN. Refer to the accompanying source code for details of how to save the captured bitmap to the disk and how to send it to the clipboard. Its pretty straightforward. The source code implements the above technique for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

And the DirectX way of doing it

Capturing the screenshot with DirectX is a pretty easy task. DirectX offers a neat way of doing this.

Every DirectX application contains what we call a buffer, or a surface to hold the contents of the video memory related to that application. This is called the back buffer of the application. Some applications might have more than one back buffer. And there is another buffer that every application can access by default – the front buffer. This one, the front buffer, holds the video memory related to the desktop contents, and so essentially is the screen image.

By accessing the front buffer from our DirectX application, we can capture the contents of the screen at that moment.

Accessing the front buffer from the DirectX application is pretty easy and straightforward. The interfaceIDirect3DDevice9 provides the GetFrontBufferData() method that takes a IDirect3DSurface9 object pointer and copies the contents of the front buffer onto that surface. The IDirect3DSurfce9 object can be generated by using the method IDirect3DDevice8::CreateOffscreenPlainSurface(). Once the screen is captured onto the surface, we can use the function D3DXSaveSurfaceToFile() to save the surface directly to the disk in bitmap format. Thus, the code to capture the screen looks as follows:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}

In the above, g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized. This code snippet saves the captured image onto the disk directly. However, instead of saving to disk, if we just want to operate on the image bits directly – we can do so by using the method IDirect3DSurface9::LockRect(). This gives a pointer to the surface memory – which is essentially a pointer to the bits of the captured image. We can copy the bits to our application defined memory and can operate on them. The following code snippet presents how the surface contents can be copied into our application defined memory:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();

In the above, pBits is a void*. Make sure that we have allocated enough memory before copying into pBits. A typical value for BITSPERPIXEL is 32 bits per pixel. However, it may vary depending on your current monitor settings. The important point to note here is that the width of the surface is not same as the captured screen image width. Because of the issues involved in the memory alignment (memory aligned to word boundaries are assumed to be accessed faster compared to non aligned memory), the surface might have added additional stuff at the end of each row to make them perfectly aligned to the word boundaries. The lockedRect.Pitch gives us the number of bytes between the starting points of two successive rows. That is, to advance to the correct point on the next row, we should advance by Pitch, not by Width. You can copy the surface bits in reverse, using the following:

for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 
        ScreenWidth * BITSPERPIXEL/8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth* BITSPERPIXEL/8);
}

This may come handy when you are converting between top-down and bottom-up bitmaps.

While the above technique of LockRect() is one way of accessing the captured image content onIDirect3DSurface9, we have another more sophisticated method defined for IDirect3DSurface9, the GetDC()method. We can use the IDirect3DSurface9::GetDC() method to get a GDI compatible device context for the DirectX image surface, which makes it possible to directly blit the surface contents to our application defined DC. Interested readers can explore this alternative.

The sample source code provided with this article implements the technique of copying the contents of an off-screen plain surface onto a user created bitmap for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

However, a point worth noting when using this technique for screen capture is the caution mentioned in the documentation: The GetFrontBufferData() is a slow operation by design, and should not be considered for use in performance-critical applications. Thus, the GDI approach is preferable over the DirectX approach in such cases.

Windows Media API for capturing the screen

Windows Media 9.0 supports screen captures using the Windows Media Encoder 9 API. It includes a codec namedWindows Media Video 9 Screen codec that has been specially optimized to operate on the content produced through screen captures. The Windows Media Encoder API provides the interface IWMEncoder2 which can be used to capture the screen content efficiently.

Working with the Windows Media Encoder API for screen captures is pretty straightforward. First, we need to start with the creation of an IWMEncoder2 object by using the CoCreateInstance() function. This can be done as:

IWMEncoder2* g_pEncoder=NULL; 
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);

The Encoder object thus created contains all the operations for working with the captured screen data. However, in order to perform its operations properly, the encoder object depends on the settings defined in what is called a profile. A profile is nothing but a file containing all the settings that control the encoding operations. We can also create custom profiles at runtime with various customized options, such as codec options etc., depending on the nature of the captured data. To use a profile with our screen capture application, we create a custom profile based on the Windows Media Video 9 Screen codec. Custom profile objects have been supported with the interfaceIWMEncProfile2. We can create a custom profile object by using the CoCreateInstance() function as:

IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);

We need to specify the target audience for the encoder in the profile. Each profile can hold multiple number of audience configurations, which are objects of the interface IWMEncAudienceObj. Here, we use one audience object for our profile. We create the audience object for our profile by using the methodIWMEncProfile::AddAudience(), which would return a pointer to IWMEncAudienceObj which can then be used for configurations such as video codec settings (IWMEncAudienceObj::put_VideoCodec()), video frame size settings (IWMEncAudienceObj::put_VideoHeight() and IWMEncAudienceObj::put_VideoWidth()) etc. For example, we set the video codec to be Windows Media Video 9 Screen codec as:

extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2') 
    //MSS2 is the fourcc for the screen codec

long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
    &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);

The fourcc is a kind of unique identifier for each codec in the world. The fourcc for the Windows Media Video 9 Screen codec is MSS2. The IWMEncAudienceObj::put_VideoCodec() accepts the profile index as the input to recognize a particular profile – which can be obtained by using the method IWMEncProfile::GetCodecIndexFromFourCC().

Once we have completed configuring the profile object, we can choose that profile into our encoder by using the method IWMEncSourceGroup :: put_Profile() which is defined on the source group objects of the encoder. A source group is a collection of sources where each source might be a video stream or audio stream or HTML stream etc. Each encoder object can work with many source groups from which it get the input data. Since our screen capture application uses only a video stream, our encoder object need to have one source group with a single source, the video source, in it. This single video source needs to configured to use the Screen Device as the input source, which can be done by using the method IWMEncVideoSource2::SetInput(BSTR) as:

extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");

The destination output can be configured to save into a video file (wmv movie) by using the methodIWMEncFile::put_LocalFileName() which requires an IWMEncFile object. This IWMEncFile object can be obtained by using the method IWMEncoder::get_File() as:

IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);

Now, once all the necessary configurations have been done on the encoder object, we can use the methodIWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() andIWMEncoder::Pause might be used for stopping and pausing the capture.

While this deals with full screen capture, we can alternately select the regions of capture by adjusting the properties of input video source stream. For this, we need to use the IPropertyBag interface of the IWmEnVideoSource2 object as:

#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );

The accompanied source code implements this technique for capturing the screen. One point that might be interesting, apart from the nice quality of the produced output movie, is that in this, the mouse cursor is also captured. (By default, GDI and DirectX are unlikely to capture the mouse cursor).

Note that your system needs to be installed with Windows Media 9.0 SDK components to create applications using the Window Media 9.0 API.

To run your applications, end users must install the Windows Media Encoder 9 Series. When you distribute applications based on the Windows Media Encoder SDK, you must also include the Windows Media Encoder software, either by redistributing Windows Media Encoder in your setup, or by requiring your users to install Windows Media Encoder themselves.

The Windows Media Encoder 9.0 can be downloaded from:

Conclusion

All the variety of techniques discussed above are aimed at a single goal – capturing the contents of the screen. However, as can be guessed easily, the results vary depending upon the particular technique that is being employed in the program. If all that we want is just a random snapshot occasionally, the GDI approach is a good choice, given its simplicity. However, using Windows Media would be a better option if we want more professional results. One point worth noting is, the quality of the content captured through these mechanisms might depend on the settings of the system. For example, disabling hardware acceleration (Desktop properties | Settings | Advanced | Troubleshoot) might drastically improve the overall quality and performance of the capture application.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

GetPixel截屏

作     者:周强 2009.05.02

 

/**************************************************************************
* 函数名称:SaveToBmpFile
* 函数功能:把客户区内由两点所确定的矩形内图像保存为bmp图像
***************************************************************************
* 入口参数:CPoint point1     矩形左上角点
*           CPoint point2     矩形右下角点
*           char *filename    保存后文件名
*           CDC* pDC          设备内容对象指针
* 出口参数:std::vector<CtlKeyPoint> &vec_point       提取的极值点的坐标
* 返 回 值:成功,返回true ; 失败返回false。
***************************************************************************
* 备    注:使用方法举例:
*                         CPoint p1(0,0);
*                           CPoint p2(1200,800);
*                         if(SaveToBmpFile(p1,p2,”图像.bmp”,pDC)==true)
*                           {
*                            AfxMessageBox(“保存成功!”);
*                           }
*作     者:周强 2009.05.02
***************************************************************************/
BOOL CMyView::SaveToBmpFile(CPoint point1, CPoint point2, char *filename,CDC* pDC)// 当宽度不是4的倍数时自动添加成4的倍数
{
 int width, height;//保存数据的宽高
 width = point2.x-point1.x;
 height = point2.y-point1.y;
 
 BYTE *pImg = new BYTE[3*width*height];//从屏幕提取要保存的图像数据
 for (int index1=0;index1<height;index1++)
 {
  for (int index2=0;index2<3*width;index2+=3)
  {
   COLORREF coler = pDC->GetPixel(index2/3,index1);
   pImg[index1*width*3+index2+0]=GetBValue(coler);
   pImg[index1*width*3+index2+1]=GetGValue(coler);
   pImg[index1*width*3+index2+2]=GetRValue(coler);
  }
 }
 
 FILE *BinFile;
    BITMAPFILEHEADER FileHeader;
    BITMAPINFOHEADER BmpHeader;
    bool Suc=true;
    int i,extend;
 BYTE *pCur;
 
    // Open File
    if((BinFile=fopen(filename,”w+b”))==NULL) {  return false; }
 // Fill the FileHeader
 FileHeader.bfType= ((WORD) (‘M’ << 8) | ‘B’);
 FileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
    FileHeader.bfSize=FileHeader.bfOffBits+width*height*3L ;
    FileHeader.bfReserved1=0;
    FileHeader.bfReserved2=0;
 if (fwrite((void *)&FileHeader,1,sizeof(BITMAPFILEHEADER),BinFile)!=sizeof(BITMAPFILEHEADER)) Suc=false;
 // Fill the ImgHeader
 BmpHeader.biSize = 40;
    BmpHeader.biWidth = width;
 BmpHeader.biHeight = height;
 BmpHeader.biPlanes = 1 ;
 BmpHeader.biBitCount = 24 ;
 BmpHeader.biCompression = 0 ;
 BmpHeader.biSizeImage = 0 ;
 BmpHeader.biXPelsPerMeter = 0;
 BmpHeader.biYPelsPerMeter = 0;
 BmpHeader.biClrUsed = 0;
 BmpHeader.biClrImportant = 0;
 if (fwrite((void *)&BmpHeader,1,sizeof(BITMAPINFOHEADER),BinFile)!=sizeof(BITMAPINFOHEADER)) Suc=false;
 // write image data
 extend=(width+3)/4*4-width;
 if (extend==0)
 {  
  for(pCur=pImg+(height-1)*3*width;pCur>=pImg;pCur-=3*width)
  {  
   if (fwrite((void *)pCur,1,width*3,BinFile)!=(unsigned int)(3*width)) Suc=false; // 真实的数据
  }
 }
 else
 {  
  for(pCur=pImg+(height-1)*3*width;pCur>=pImg;pCur-=3*width)
  {  
   if (fwrite((void *)pCur,1,width*3,BinFile)!=(unsigned int)(3*width)) Suc=false; // 真实的数据
   for(i=0;i<extend;i++) // 扩充的数据
   {
    if (fwrite((void *)(pCur+3*(width-1)+0),1,1,BinFile)!=1) Suc=false;
    if (fwrite((void *)(pCur+3*(width-1)+1),1,1,BinFile)!=1) Suc=false;
    if (fwrite((void *)(pCur+3*(width-1)+2),1,1,BinFile)!=1) Suc=false;
   }
  }
 }
 // return;
 fclose(BinFile);
 delete []pImg;
 return Suc;
}

C#中三种截屏方式总结

[转载自:http://www.yqdown.com/chengxukaifa/CC/4012.htm]

昨天写自动化测试的CASE的时候,碰到一个疑难杂症,调用截图的函数去截取一个Popup窗口,但是总是把背景程序给截下来,Popup窗口就跟看不到一样。本来以为是同步的问题,也就是以为先截图再点击弹出Popup窗口了。后来加了N个Thread.Sleep来测试,发觉根本不是因为这个原由,而是截图的函数截不下来这个窗口。

这个为啥呢,只好把截图的函数代码翻出来看,以前是用这种方式的:
BitBlt(dcImage, 0, 0, (int)(rect.Width), (int)(rect.Height), dcScreen, (int)(rect.Left), (int)(rect.Top), TernaryRasterOperations.SRCCOPY);

凭直觉感觉应该是因为这种通过DC的方式对WPF程序支持有问题,但是又觉得奇怪就是截取其它的WPF组件和窗口都没有问题,偏偏Popup窗口不可以。

前些天听说另外一种截屏的要领,这种要领连被遮挡的窗口都可以截,于是就Google一大把,找打了PrintWindow函数,于是就有了第二种处理方案,代码如下:

IntPtr hdc = Native.GetWindowDC(this.Handle);
if (hdc != IntPtr.Zero)
{
    IntPtr hdcMem = Native.CreateCompatibleDC(hdc);
    if (hdcMem != IntPtr.Zero)
    {
        IntPtr hbitmap = Native.CreateCompatibleBitmap(hdc, (int)(Rect.Width), (int)(Rect.Height));
        if (hbitmap != IntPtr.Zero)
        {
            Native.SelectObject(hdcMem, hbitmap);
            Native.PrintWindow(this.Handle, hdcMem, 0);

            Native.DeleteObject(hbitmap);
            Bitmap bmp = Bitmap.FromHbitmap(hbitmap);
            bmp.Save(sPath);
       }
        Native.DeleteObject(hdcMem);
    }
    Native.ReleaseDC(this.Handle, hdc);
}

 

就是拿到窗口的句柄,通过PrintWindow API来截取窗口。

但是更让人气愤的事情出现了,截出来的窗口中,只要是用到WPF组件的地点,全部是黑块儿,只有MFC的窗口框架和按钮可以正常被截取。

于是乎,就无奈的继续分析这个问题,我记得WPF是没有走GDI,而是通过Directx渲染的,那就是说DC的方式和PrintWindow的方式都不靠谱,但是截Directx的貌似还比较复杂。

突然想起来,平常报bug的时候都是按PrintScreen,然后再处理一下的,那应该PrintScreen按键是管用的,看来只能曲线救国了。但是那样就得走剪切板了,貌似会破坏剪切板的数据,不过如果我在截取前保存一下数据,在截取后再恢复一下剪切板数据,那就没有问题了。

于是就有了第三种处理方案(暂时还没有加恢复剪切板数据的代码):

const uint KEYEVENTF_EXTENDEDKEY = 0x1;
const uint KEYEVENTF_KEYUP = 0x2;
const byte VK_SNAPSHOT = 0x2C;
Native.keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY, UIntPtr.Zero);
Native.keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY KEYEVENTF_KEYUP, UIntPtr.Zero);

IDataObject iObj = Clipboard.GetDataObject();
if (iObj.GetDataPresent(DataFormats.Bitmap, true))
{
    Bitmap bmpScreen = iObj.GetData(DataFormats.Bitmap, true) as Bitmap;
    Bitmap bmpOutput = new Bitmap((int)this.Rect.Width, (int)this.Rect.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    Graphics g = Graphics.FromImage(bmpOutput);
    Rectangle destRectangle = new Rectangle(0, 0, (int)this.Rect.Width, (int)this.Rect.Height);
    g.DrawImage(bmpScreen,destRectangle,  (int)this.Rect.X, (int)this.Rect.Y, (int)this.Rect.Width, (int)this.Rect.Height, GraphicsUnit.Pixel);

 

    bmpOutput.Save(sPath, System.Drawing.Imaging.ImageFormat.Bmp);
}

 

测试可用,只好先用着了

不过还有多个问题,先写下来,留待以后处理:

1. 针对第三种方案,既然可以按PrintScreen键截图,那对应的API是什么,总觉得发键盘消息没有直接调API稳定

2. 针对WPF截图有没有更好的处理方案

 

 

 

DirectDraw下的的游戏截屏

  现在很多游戏都提供一种截屏的功能,用来截取游戏中的画面,那么这是如何实现的呢?其实就是把游戏当前屏幕的数据存成一个图片文件;在这里我给出一段源程序,它实现了DirectDraw(16位模式)下的的游戏截屏。生成的文件格式为BMP,程序有比较详细的注释,请各位自己看吧。

bool Is555; // 是否是565模式,这个变量需要用者填写

// 功能:将一个16位的DirectDraw表面,存为一张24位BMP位图 (传入主表面即截屏)
// 输入:表面指针,输出的文件名
// 输出:是否成功
bool SaveToBitmapFile(LPDIRECTDRAWSURFACE lpSurface, char* filename)
{
    WORD* lpBuffer; // 表面指针
    int nPitch; // 表面跨距
    int nWidth, nHeight; // 表面宽高

    // 打开文件s
    FILE* fp;
    if( (fp=fopen(filename, “wb”)) != NULL )
    {
        // 锁定表面
        DDSURFACEDESC ddsd;
        ddsd.dwSize = sizeof(ddsd);
        HRESULT ddrval = lpSurface->Lock( NULL, &ddsd, DDLOCK_WAIT, NULL );
        if( ddrval == DD_OK )
        {
            lpBuffer = (WORD *)ddsd.lpSurface;
            nWidth = ddsd.dwWidth;
            nHeight = ddsd.dwHeight;
            nPitch = ddsd.lPitch >> 1; //lPitch以Byte为单位,GraphPitch以WORD为单位。所以GraphPitch = lPitch / 2;
        }

        // 保存文件头
        BITMAPFILEHEADER FileHeader;
        FileHeader.bfType = ‘BM’;
        FileHeader.bfSize = nWidth * nHeight * 3 + 0x36;
        FileHeader.bfReserved1 = 0;
        FileHeader.bfReserved2 = 0;
        FileHeader.bfOffBits = 0x36;
        fwrite(&FileHeader, sizeof(BITMAPFILEHEADER), 1, fp);

        // 保存文件信息
        BITMAPINFOHEADER Header;
        Header.biSize = sizeof(BITMAPINFOHEADER); // 结构的大小
        Header.biWidth = nWidth; // 宽
        Header.biHeight = nHeight; // 高
        Header.biPlanes = 1; // 固定
        Header.biBitCount = 24; // 颜色数
        Header.biCompression = BI_RGB; // 是否压缩
        Header.biSizeImage = nWidth * nHeight * 3; // 图片的大小
        Header.biXPelsPerMeter = 0;
        Header.biYPelsPerMeter = 0;
        Header.biClrUsed = 0;
        Header.biClrImportant = 0;
        fwrite(&Header, Header.biSize, 1, fp);

        // 写入具体内容(从下向上存放)
        fseek(fp, 0x36, SEEK_SET);
        WORD word;
        lpBuffer += nWidth * (nHeight – 1);
        for(int i=0; i
{
            for(int j=0; j
{
               
word = *lpBuffer;
               
fputc( GetBlue( word ), fp); // 蓝
               
fputc( GetGreen( word ), fp); // 绿
               
fputc( GetRed( word ), fp); // 红
               
lpBuffer++;
           
}
           
lpBuffer -= nPitch*2; // 指针转到上一行的开始
       
}

        fclose(fp);

        // 解锁表面
       
ddrval = lpSurface->Unlock( NULL );
       
return true;
   
}

    return false;
}

inline unsigned char GetRed(WORD color)
{
   
if( Is555 )
       
return (color>>7) & 0xff;
   
else
       
return (color>>8) & 0xff;
}

inline unsigned char GetGreen(WORD color)
{
   
if( Is555 )
       
return (color>>2) & 0xff;
   
else
       
return (color>>3) & 0xff;
}

inline unsigned char GetBlue(WORD color)
{
   
return (color & 0x1f) << 3;
}

使用DirectX截屏

网上有很多关于DirectX截屏的文章,但大都是屏幕截图,很少有窗口截图,本文则两者都涉及到,先讲如何截取整个屏幕,再讲如何截取某个窗口,其实二者的区别不大,只是某个参数的设置不同而已,最后我们还将扩展到任意区域的截图。

首先看一下截屏用到的函数,最核心的当然是D3DXSaveSurfaceToFile,先看下函数原型

  1. 1 HRESULT D3DXSaveSurfaceToFile(
  2. 2  LPCTSTR pDestFile,
  3. 3  D3DXIMAGE_FILEFORMAT DestFormat,
  4. 4  LPDIRECT3DSURFACE9 pSrcSurface,
  5. 5  CONST PALETTEENTRY * pSrcPalette,
  6. 6  CONST RECT * pSrcRect
  7. 7 );

复制代码

第一个参数是指向设备的指针,不多说啦

第二个参数是截图文件的类型,支持的类型还不少,主要有下面这些

BMP,JPG,TGA,PNG,DDS,PPM,DIB,HDR,PFM

这里我们使用BMP-即位图格式

第三个参数是指向Surface的指针,也就是保存了截图数据的表面

第四个参数是Surface的调色板,这里不使用,设置为NULL

最后一个参数是Surface的矩形区域,也就是我们可以只截取Surface上某一矩形区域的数据,其实截取全屏和截取窗口的差别也就在这个参数的设置上

其他的函数在下面会逐一讲解

现在来定义我们的截屏函数,首先我们需要一个设备指针,因为在DX中,任何操作都与设备密切相关,所以设备指针几乎是每个DX函数都要用到的参数,我们这个函数也不例外,齐次需要一个窗口句柄,当我们截取窗口时,把窗口句柄传入,当我们截取整个屏幕时,直接传入NULL。最后我们需要一个字符串参数来指定截图对应的文件名,如下
1 BOOL ScreenShot(LPDIRECT3DDEVICE9 lpDevice, HWND hWnd, TCHAR* fileName)

详细步骤:

首先我们需要获取显示模式,注意这里获取的是显卡的显示模式,而不是设备的显示模式,因为设备的显示模式既有窗口模式,也有全屏模式,所以它的分辨率是不确定的,而显卡的显示模式返回的始终是最大分辨率,我们需要创建整个屏幕区域对应的Surface,当截取真个屏幕时,直接保存即可,当截取窗口时,我们将窗口所对应的区域保存即可

获取显卡显示模式的代码如下

  1. 1 HRESULT hr;
  2. 2
  3. 3 // Get adapter display mode
  4. 4 D3DDISPLAYMODE mode;
  5. 5 if (FAILED(hr = lpDevice->GetDisplayMode(0, &mode)))
  6. 6    return hr;
  7. 7

复制代码

下面开始创建表面,这个表面是对应整个屏幕的

  1. 1 // Create the surface to hold the screen image data
  2. 2 LPDIRECT3DSURFACE9 surf;
  3. 3 if (FAILED(hr = lpDevice->CreateOffscreenPlainSurface(mode.Width,
  4. 4    mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surf, NULL))) //注意第四个参数不能是D3DPOOL_DEFAULT
  5. 5 {
  6. 6    return hr;
  7. 7 }
  8. 8

复制代码

接下来获取屏幕对应的数据,这个函数实际上是将显存中的数据拷贝到系统内存中

  1. 1 // Get the screen data
  2. 2 if (FAILED(hr = lpDevice->GetFrontBufferData(0, surf)))
  3. 3 {
  4. 4    surf->Release() ;
  5. 5    return hr ;
  6. 6 }
  7. 7

复制代码

接下来我们判断是截取窗口还是截取屏幕,很简单,只需判断hWnd是否为NULL即可,如果是截取窗口则设置窗口对应的矩形区域即可

  1. 1 // area to capture
  2. 2 RECT *rect = NULL ;
  3. 3
  4. 4 WINDOWINFO windowInfo ;
  5. 5 windowInfo.cbSize = sizeof(WINDOWINFO) ;
  6. 6
  7. 7 if(hWnd) // capture window
  8. 8 {
  9. 9    GetWindowInfo(hWnd, &windowInfo) ;
  10. 10    rect = &windowInfo.rcWindow ;
  11. 11 }
  12. 12

复制代码

最后一部,保存截图!

  1. 1 // Save the screen date to file
  2. 2 hr = D3DXSaveSurfaceToFile(fileName, D3DXIFF_BMP, surf, NULL, rect);
  3. 3
  4. 4 surf->Release() ;
  5. 5
  6. 6 return hr ;
  7. 7

复制代码

大功告成!

完整代码

  1. 1 BOOL ScreenShot(LPDIRECT3DDEVICE9 lpDevice, HWND hWnd, TCHAR* fileName)
  2. 2 {
  3. 3    HRESULT hr;
  4. 4   
  5. 5    // Get adapter display mode
  6. 6    D3DDISPLAYMODE mode;
  7. 7    if (FAILED(hr = lpDevice->GetDisplayMode(0, &mode)))
  8. 8        return hr;
  9. 9
  10. 10    // Create the surface to hold the screen image data
  11. 11    LPDIRECT3DSURFACE9 surf;
  12. 12    if (FAILED(hr = lpDevice->CreateOffscreenPlainSurface(mode.Width,
  13. 13        mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surf, NULL))) //注意第四个参数不能是D3DPOOL_DEFAULT
  14. 14    {
  15. 15        return hr;
  16. 16    }
  17. 17
  18. 18    // Get the screen data
  19. 19    if (FAILED(hr = lpDevice->GetFrontBufferData(0, surf)))
  20. 20    {
  21. 21        surf->Release() ;
  22. 22        return hr ;
  23. 23    }
  24. 24
  25. 25    // area to capture
  26. 26    RECT *rect = NULL ;
  27. 27
  28. 28    WINDOWINFO windowInfo ;
  29. 29    windowInfo.cbSize = sizeof(WINDOWINFO) ;
  30. 30
  31. 31    if(hWnd) // capture window
  32. 32    {
  33. 33        GetWindowInfo(hWnd, &windowInfo) ;
  34. 34        rect = &windowInfo.rcWindow ;
  35. 35    }
  36. 36
  37. 37    // Save the screen date to file
  38. 38    hr = D3DXSaveSurfaceToFile(fileName, D3DXIFF_BMP, surf, NULL, rect);
  39. 39
  40. 40    surf->Release() ;
  41. 41
  42. 42    return hr ;
  43. 43 }

复制代码

那么如何实现任意区域截屏呢,我想大家已经想到了,假设使用鼠标拖拽的方法截图,记下鼠标按下和抬起时的坐标,构造一个RECT,然后传递给 D3DXSaveSurfaceToFile函数就可以了,需要注意到是,由于鼠标拖拽到方向是任意的,所以在构造RECT的时候要注意right < left或者bottom < top 的情况,用下面的方法可以处理

  1. 1 int left = 0 ;
  2. 2 int right = 0 ;
  3. 3 int top = 0 ;
  4. 4 int bottom = 0 ;
  5. 5 RECT rect ;
  6. 6
  7. 7 case WM_LBUTTONDOWN:
  8. 8    left = ( short )LOWORD( lParam );
  9. 9    top = ( short )HIWORD( lParam );
  10. 10    break ;
  11. 11
  12. 12 case WM_LBUTTONUP:
  13. 13    right = ( short )LOWORD( lParam );
  14. 14    bottom = ( short )HIWORD( lParam );
  15. 15
  16. 16    rect.left = min(left, right) ;
  17. 17    rect.right = max(left, right) ;
  18. 18    rect.top = min(top, bottom) ;
  19. 19    rect.bottom = max(top, bottom) ;
  20. 20    // 调用截图函数
  21. 21

复制代码

(文/AutumnWinter

来自:http://www.pin5i.com/showtopic-26129.html

 

晒一下c#截屏对象code

 

昨天,因为需要一个截屏的功能所以自己就写了一下。第一版是一个单纯的利用.net类库来实现的屏幕方式。

        static public void PrintSystemScreen (string file) {

            
if (string.IsNullOrEmpty(file)) {
                
// Create a random file name by GUID and set the saved 
                
// directory at current directory.
                file = Guid.NewGuid().ToString();
            }


            
// Create a bitmap for save
            Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
                                    Screen.PrimaryScreen.Bounds.Height,
                                    PixelFormat.Format32bppPArgb);

            
// Create a graphic for drawing from bmp
            Graphics screenG = Graphics.FromImage(bmp);
            screenG.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
                                   Screen.PrimaryScreen.Bounds.Y,
                                   
00,
                                   Screen.PrimaryScreen.Bounds.Size,
                                   CopyPixelOperation.SourceCopy);

            ImageParams imgParam 
= new ImageParams(file);
            bmp.Save(imgParam.FilePath, imgParam.CodeInfo, imgParam.EncoderParam);
        }

第二版,我加入了根据给出的应用程序主窗口去截取界面的功能,使用Win32 Api来实现:

    /// <summary>
    
/// GeneralScreen include serval functions.
    
/// Capture single screenshot.
    
/// Capture application screenshot.
    
/// !Capture web page screenshot.
    
/// </summary>

    static public class GeneralScreen {

        
/// <summary>
        
/// Capturing the current screen operation then save
        
/// image to the indicated file.
        
/// </summary>
        
/// <param name=”file”></param>

        static public void PrintSystemScreen (string file) {
            IntPtr screenHandle 
= NativeMethods.GetDesktopWindow();
            Capture(screenHandle, file);
        }


        
/// <summary>
        
/// Create a random file name by GUID under the runtime directory.
        
/// </summary>

        static public void PrintSystemScreen () {
            PrintSystemScreen(
null);
        }


        
/// <summary>
        
/// Capturing the indicated application window operation then save
        
/// image to the indicated file. If the file parameter not set, function will
        
/// create a random file name by GUID under the runtime directory.
        
/// </summary>
        
/// <param name=”handle”>window’s handle</param>
        
/// <param name=”file”>target image file</param>

        static public void PrintApplicationScreen (IntPtr handle, string file) {
            
if (NativeMethods.SetForegroundWindow(handle)) {
                Capture(handle, file);
            }

        }



        
#region Helper

        
/// <summary>
        
/// Capture the indicated region screenshot by the indicated window.
        
/// </summary>
        
/// <param name=”handle”>indicated window handle</param>
        
/// <param name=”file”>save image file name</param>

        static private void Capture (IntPtr handle, string file) {
            
// Testing source handle
            if (handle != IntPtr.Zero) {
                NativeMethods.RECT srcRect;

                
// Get the source window’s information
                if (NativeMethods.GetWindowRect(handle, out srcRect)) {
                    
int width = srcRect.Right  srcRect.Left;
                    
int height = srcRect.Bottom  srcRect.Top;
                    Capture(srcRect.Left, srcRect.Top, width, height, file);
                }

            }

        }



        
/// <summary>
        
/// Capture the screenshot by coordinate from display memory.
        
/// </summary>
        
/// <param name=”x”></param>
        
/// <param name=”y”></param>
        
/// <param name=”width”></param>
        
/// <param name=”height”></param>
        
/// <param name=”file”></param>

        static private void Capture (int x, int y, int width, int height, string file) {
            System.Drawing.Image targetImg;
            IntPtr displayDC 
= IntPtr.Zero;
            IntPtr compatibleDC 
= IntPtr.Zero;
            IntPtr bmp 
= IntPtr.Zero;

            
try {

                displayDC 
= NativeMethods.CreateDC(DISPLAY,
                                                   IntPtr.Zero,
                                                   IntPtr.Zero,
                                                   IntPtr.Zero);
                
if (displayDC == IntPtr.Zero) {
                    
throw new Exception(CreateDC failed!);
                }


                compatibleDC 
= NativeMethods.CreateCompatibleDC(displayDC);
                
if (compatibleDC == IntPtr.Zero) {
                    
throw new Exception(CreateCompatibleDC failed!);
                }


                bmp 
= NativeMethods.CreateCompatibleBitmap(displayDC, width, height);

                
// set DC relate to bmp
                if (NativeMethods.SelectObject(compatibleDC, bmp) == IntPtr.Zero) {
                    
throw new Exception(CreateCompatibleBitmap failed);
                }


                
if (0 == NativeMethods.BitBlt(compatibleDC, 00, width, height,
                                              displayDC, x, y,
                                              
0xcc0020)) {
                    
throw new Exception(BitBlt failed);
                }


                targetImg 
= System.Drawing.Image.FromHbitmap(bmp);

                
// Save image
                
//
                if (string.IsNullOrEmpty(file)) {
                    
// Create a random file name by GUID and set the saved 
                    
// directory at current directory.
                    file = Guid.NewGuid().ToString();
                }


                ImageParams imgParam 
= new ImageParams(file);
                targetImg.Save(imgParam.FilePath, imgParam.CodeInfo, imgParam.EncoderParam);
            }
 finally {
                NativeMethods.DeleteDC(displayDC);
                NativeMethods.DeleteDC(compatibleDC);
                NativeMethods.DeleteObject(bmp);
            }

        }

        
#endregion

    }



    
/// <summary>
    
/// The nativeMethods includes the win32 api for the others 
    
/// class calling.
    
/// </summary>

    internal static class NativeMethods {
        
// Methods
        [DllImport(gdi32.dll, SetLastError = true, ExactSpelling = true)]
        
internal static extern int BitBlt (IntPtr destDC, int xDest, int yDest, int width, int height, IntPtr sourceDC, int xSource, int ySource, uint rasterOperation);
        [DllImport(
gdi32.dll, SetLastError = true, ExactSpelling = true)]
        
internal static extern IntPtr CreateCompatibleBitmap (IntPtr dc, int width, int height);
        [DllImport(
gdi32.dll, SetLastError = true, ExactSpelling = true)]
        
internal static extern IntPtr CreateCompatibleDC (IntPtr dc);
        [DllImport(
gdi32.dll, EntryPoint = CreateDCW, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
        
internal static extern IntPtr CreateDC (string driver, IntPtr device, IntPtr output, IntPtr devMode);
        [DllImport(
gdi32.dll, ExactSpelling = true)]
        
internal static extern int DeleteDC (IntPtr dc);
        [DllImport(
gdi32.dll, ExactSpelling = true)]
        
internal static extern int DeleteObject (IntPtr gdiObject);
        [DllImport(
gdi32.dll, SetLastError = true, ExactSpelling = true)]
        
internal static extern IntPtr SelectObject (IntPtr dc, IntPtr gdi);

        [DllImport(
user32.dll)]
        [
return: MarshalAs(UnmanagedType.Bool)]
        
internal static extern bool GetWindowRect (IntPtr hWnd, out RECT lpRect);
        [StructLayout(LayoutKind.Sequential)]
        
public struct RECT {
            
public int Left;
            
public int Top;
            
public int Right;
            
public int Bottom;
        }


        [DllImport(
user32.dll, ExactSpelling = true, CharSet = CharSet.Auto)]
        [
return: MarshalAs(UnmanagedType.Bool)]
        
internal static extern bool SetForegroundWindow (IntPtr hWnd);

        [DllImport(
user32.dll)]
        
public static extern IntPtr GetDesktopWindow ();
    }

在这里我用了一个辅助的类ImageParam,这个类可以根据给出的文件名自己识别需要生成什么格式的图片文件。
完整的Demo工程从这里下载

基于MFC的截屏

#include “stdafx.h”
#include <afxwin.h>

void Screen(char filename[])
{
    CDC *pDC;//屏幕DC
    pDC CDC::FromHandle(GetDC(NULL));//获取当前整个屏幕DC
    int BitPerPixel pDC->GetDeviceCaps(BITSPIXEL);//获得颜色模式
    int Width pDC->GetDeviceCaps(HORZRES);
    int Height pDC->GetDeviceCaps(VERTRES);

    printf(“当前屏幕色彩模式为%d位色彩\n”, BitPerPixel);
    printf(“屏幕宽度:%d\n”, Width);
    printf(“屏幕高度:%d\n”, Height);
    
    CDC memDC;//内存DC
    memDC.CreateCompatibleDC(pDC);
    
    CBitmap memBitmap, *oldmemBitmap;//建立和屏幕兼容的bitmap
    memBitmap.CreateCompatibleBitmap(pDC, Width, Height);

    oldmemBitmap memDC.SelectObject(&memBitmap);//将memBitmap选入内存DC
    memDC.BitBlt(0, 0, Width, Height, pDC, 0, 0, SRCCOPY);//复制屏幕图像到内存DC

    //以下代码保存memDC中的位图到文件
    BITMAP bmp;
    memBitmap.GetBitmap(&bmp);//获得位图信息
    
    FILE *fp fopen(filename, “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;
    
    fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);//写入位图文件头
    
    fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);//写入位图信息头
    
    byte new byte[bmp.bmWidthBytes bmp.bmHeight];//申请内存保存位图数据

    GetDIBits(memDC.m_hDC, (HBITMAP) memBitmap.m_hObject, 0, Height, p, 
        (LPBITMAPINFO) &bih, DIB_RGB_COLORS);//获取位图数据

    fwrite(p, 1, bmp.bmWidthBytes bmp.bmHeight, fp);//写入位图数据

    delete [] p;

    fclose(fp);

    memDC.SelectObject(oldmemBitmap);
}