Click here to Skip to main content
Click here to Skip to main content

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 with SM_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 interface IDirect3DDevice9 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 on IDirect3DSurface9, 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 named Windows 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 interface IWMEncProfile2. 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 method IWMEncProfile::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 method IWMEncFile::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 method IWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() and IWMEncoder::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)

About the Author

Gopalakrishna Palem
India India
Member
Creator of CFugue Runtime Environment for MusicNote Programming in C++

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
AnswerRe: WM Screen Capture is throwing errormemberrajutrivium20 Dec '11 - 2:46 
if(FAILED(CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,IID_IWMEncoder2,(void**)&g_pEncoder))) Try this
 

It will depend upon whether your class is plain cpp or windowed application.Even if you try when encoder is running it will give an error.
QuestionNot able to install & use Windows Encoder. Also GDI gives poor resultmemberHansaP5 Aug '11 - 0:17 
Hi,
 
You have provided a very good article. I want to create Screen recorder. I have created a code to create AVI. but, it create huge AVI file. only recording of 1-2 minutes create AVI file of size more than 2 GB.
I have also tried creating WMV but resolution is very poor.
I also tried to install & use Windows Encoder but windows encoder seriese 9 SDK do not installed DLLs & Library files. So i tried to install Windows Express Encoder 4 but is fails to install.
Hansa

AnswerRe: Not able to install & use Windows Encoder. Also GDI gives poor resultmemberP.Gopalakrishna5 Aug '11 - 2:43 
What is the code you are using to create the AVI file?
 
Note that AVI files can be huge in size if you store the captured content as is. The reduce size, you have to use Video compression codecs (such as DivX etc...)
 
For screen capture contect, the Windows Screen Codec is the best one. You can even try MPG4. With these you should see very small avi foot print.
 
In your code, ensure you are using a proper codec to compress the frames.
 
Thank you,
Gopalakrishna Palem
http://gpalem.web.officelive.com
GeneralNice Article on the Windows Media Encoder 9 screen capture...memberDestiny77718 Mar '11 - 14:21 
Nice sample code.
 
Thank you for this article.
 
Smile | :)
QuestionHi How to change the video Height and WidthmemberGeorge Zeno18 Feb '11 - 6:13 
Hi,
 
I downloaded this article and its working fine in my machine. Now i want to change the output video height and width, Bcoz   the output video height and width is same as my laptop scree width and height. Please help to change the height and width for the output video..
 

Thanks in Advance
AnswerRe: Hi How to change the video Height and WidthmemberP.Gopalakrishna18 Feb '11 - 12:57 
Please refer the old messages here and also at Create a movie from an HBitmap[^]
 
They indicate the mechanism for changing the size. The exact method depends on your technique. If you are using Windows Media Encoder, you need to create a new capture profile with appropriate capture window size settings.
 
With GDI and DirectX, its just a matter of size of your captured bitmap.
 
However, Note that not all video encoders support all sizes. You need to use the right encoder with right screen size.
 

Thank you,
Gopalakrishna Palem
Creator of CFugue Runtime for MIDI Score Programming
http://gpalem.web.officelive.com/CFugue.html
QuestionQuery on Window Media Encode(WME)r.memberMember 401378416 Feb '11 - 22:13 
Hi,
I wanted to use WME to capture full screen and send it to a remote computer. Please let me know if this will be the better option over GDI screen capture? Also let me know if I need to have decoder to decode data.
 
Sorry for the basic question.
AnswerRe: Query on Window Media Encode(WME)r.memberP.Gopalakrishna17 Feb '11 - 14:24 
Windows Media Encoder is the best choice, since it will take care of all resource management and other network related issues.
 
On the remote machine you need to have Windows Media Player to receive the transmission from the Windows Media Encoder of your souce machine. This is like live-broadcast, except that you are sending live screen capture data instead of some movie or some other video
 
Hope this helps
 
Thank you
- Gopalakrishna Palem
http://gpalem.web.officelive.com/
GeneralRe: Query on Window Media Encode(WME)r.memberMember 401378422 Feb '11 - 0:57 
Thanks for your response. I am having some more doubts...
 
1) Are you saying encoder is capable of sending and receiving data? If yes, what are interfaces available for that? Any hint or clue will help.
 
2) I wanted to get the data into a buffer (not in a file). Could you please let me know how to do it?
 
3) Do I need to have a decoder to use encoded data in my application?
GeneralRe: Query on Window Media Encode(WME)r.memberP.Gopalakrishna22 Feb '11 - 13:56 
Windows Media Encoder SDK comes with samples on how to use the streaming mechanism. With this you should be able to directly use Windows Media player on the client machine (As a decoder). No need for extra buffers. Please check the Windows Media Encoder SDK Sample, they are simple and straight to the point, easy to understand.
 

However, if you use your own capture mechanism such as GDI, then you can have the captured data onto your buffers and then need to write a network transporter and have a decoder on the client end.
 
Thank you,
Gopalakrishna Palem
Creator of CFugue C++ Runtime for MIDI Score Programming
http://gpalem.web.officelive.com/CFugue.html
GeneralRe: Query on Window Media Encode(WME)r.memberMember 401378422 Feb '11 - 23:15 
Thanks for your prompt reply and suggestion.
Actually I want to run my application on cross platforms e.g. windows PC --> Mac(IPAD) and I am not sure if something similar to WME available for IPAD. Please suggest.
 
Sorry for asking too many questions.
GeneralRe: Query on Window Media Encode(WME)r.memberP.Gopalakrishna24 Feb '11 - 22:25 
If you are capturing on Windows and displaying on IPAD, you just need WIndows MEdia PLayer on IPAD, or atleast something that is compatible with Windows MEdia PLayer is needed on IPAD.
 
Not sure if such things exist though.
 
- Gopalakrishna Palem
GeneralBitBlt failed on windows 7 machine [modified]membermsurni6 Jan '11 - 19:15 
Hi,
 
I am running screen capture application as a service on Windows 7 machine. Screen capturing is not happening as BitBlt failed due to empty device handle returned. If I run my screen capture application as an application instead of service. Screen capture is happening fine.
 
Please do let me know how to resolve this issue.
 
Thanks,
Manjula Reddy

modified on Friday, January 7, 2011 6:00 AM

GeneralRe: BitBlt failed on windows 7 machinememberP.Gopalakrishna7 Jan '11 - 0:26 
Please check under which user the service is running and if it has access to the desktop.
 
Also, it should be noted that most of the times Services do not run in interactive mode, which means the GUI handles and UI interaction capabilities will not work.
 
Thank you,
Gopalakrishna Palem
http://gpalem.web.officelive.com/[^]
GeneralRe: BitBlt failed on windows 7 machinemembermsurni9 Jan '11 - 19:52 
Hi,
 
Am running the service with "local system" account. I should be able to run the service forver and it should do screen capturing if any user logsin and logs off.
 
Thanks,
Manjula Reddy
GeneralGreat Article! I heard about one more way.memberIssaharNoam31 Oct '10 - 21:49 
Thanks for the great article.
My rate is 5
 
I heard about the mirroring screen adaptor driver method. Did you hear something about that?
GeneralError in pEncoder->Start()memberJunlin Xu15 Sep '10 - 7:38 
I was able to compile your code. But the function call pEncoder->Start() returns E_NOTIMPL. Do you have any idea about this error?
 
Thanks
GeneralRe: Error in pEncoder->Start()memberP.Gopalakrishna16 Sep '10 - 4:42 
It indicates an invalid interface.
 
Do you have the Windows Media Encoder installed and working properly ?
 
The encoder classes seems to have some problem finding the right implementation.
 
Gopalakrishna Palem
Creator of CFugue
Generalwhich is fastestmemberRogerPack19 Jul '10 - 10:35 
Anybody done any benchmarking on these three then?
GeneralRe: which is fastestmemberP.Gopalakrishna23 Oct '11 - 17:18 
GDI
GeneralCapturing just one windows---use GDImemberRogerPack19 Jul '10 - 10:34 
NB that using BitBlt can capture a window even if it is partially off the screen or is obscured by other windows.
GeneralMy vote of 5memberRogerPack19 Jul '10 - 10:34 
excellentwriteup
GeneralUsing DirectX to Capture Game Screens in C#membercokesnuff15 Jun '10 - 13:00 
I wouldn't go as far to say it cannot be done. You can do it, however, you will have to write .COM interops to access the members as they are written as C/C++.
 
There is already some work out there for using DirectX with Managed Code, however it relies on the DirectShow.Net library. And depending on what you are using the DirectShow.Net library for, I am uncertain whether the license they give allows you to develop and sell an assembly that is derived or depends on this library.
Questionso how do i capture directx games in full screen using C#?memberRoy Barina26 May '10 - 8:21 
please help me..
how do i capture full screen 3d games screenshots using directx reference in C#?
AnswerRe: so how do i capture directx games in full screen using C#?memberP.Gopalakrishna26 May '10 - 15:08 
The Simple answer is: "You Can not"
 
To capture a DirectX content, you need to have its surface pointer with you. Please check the "Recording DirectX Simulations" from my articles.
 
Thank you,
Gopalakrishna Palem
Creator of CFugue Runtime for Midi score programming
CFugue

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 19 Sep 2006
Article Copyright 2003 by Gopalakrishna Palem
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid