Search This Blog

Thursday, January 8, 2009

Capturing a screen shot in .NET 2.0 Windows Application

When supporting installed software, descriptions of problems can be enhanced greatly by viewing the contents of the user's screen. In this article we will explore how to perform a screen grab and display the captured image in a Windows Forms application.

Creating the Application

It was not possible to capture the contents of the desktop using the early versions of the .NET framework without resorting to Windows API calls. The .NET framework 2.0 introduced several new and modified classes within the System.Drawing and System.Drawing.Imaging namespaces that make the job much simpler. Instead of using API calls, the Graphics Device Interface 'plus' (GDI+) system that was introduced in Windows XP, and is provided in later operating systems, is utilised.

To demonstrate, we will create a simple Windows Forms program that copies the contents of the screen onto the background of its main form on loading. The image will be the same as that copied to the clipboard when the Print Screen key is pressed. To begin, create a new Windows Forms application. The entire code for this sample will be held in the form's Load event so add this event using the development environment of your choice. The generated code should be similar to the following:

private void Form1_Load(object sender, EventArgs e) {  }

The process uses two key drawing namespaces. To reference these, ensure that the following using directives appear at the top of the code file:

using System.Drawing; using System.Drawing.Imaging;

Creating the Bitmap

The first task for the program is to create the bitmap image that will be used to hold the captured desktop contents. This image will, at the end of the process, be displayed within the application's window as the background image. It could alternatively be saved to disk or displayed in another image control according to the program's requirements.

Bitmap Size

The bitmap image will be created with a fixed size. This size must match the screen resolution exactly or the image will be cropped or contain blank areas that are simply wasting resources. To determine the screen resolution, add the following code to the Load event:

Rectangle bounds = Screen.PrimaryScreen.Bounds;

NB: In the example we are using the PrimaryScreen property to examine the contents of the main display. If multiple monitors are in use, the AllScreens array can be used to capture the other displays.

Colour Depth

The colour depth for the screen is measured in bits per pixel. Usual values for number of bits used per pixel are 8, 16, 24 and 32. The higher the number of bits, the more colours can be represented in each screen pixel giving improvements in graphical quality at the cost of memory usage. We will identify the colour depth and use a similar value for the bitmap so that memory usage is minimised.

To determine the colour depth, add the following code:

int colourDepth = Screen.PrimaryScreen.BitsPerPixel;

Selecting the Pixel Format

To create a bitmap object with a specific number of bits per pixel, a PixelFormat structure must be created. This data type specifies information relating to pixel formats that may be used by GDI+ when working with images. The structure contains some pre-defined constants that can be used to specify the various bit-depths required.

One limitation of the screen capturing method  is the inability to capture a bitmap with eight bits per pixel. This is due to the image being pallet-based rather than defined as an RGB value. To avoid this problem, if the screen is set to use 256 colours (8-bit), the captured image will use a sixteen-bit colour depth.

The following code creates a new PixelFormat structure and assigns its value according to the colour depth identified earlier. The switch statement includes a default option that uses thirty-two bits per pixel. This is used if the colour depth is not one of the expected values.

PixelFormat format; switch (colourDepth) {     case 8:     case 16:         format = PixelFormat.Format16bppRgb565;         break;      case 24:         format = PixelFormat.Format24bppRgb;         break;      case 32:         format = PixelFormat.Format32bppArgb;         break;      default:         format = PixelFormat.Format32bppArgb;         break; }

Initialising the Bitmap

Now that the size and number of colours for the final image is known, a Bitmap object can be initialised. Add the following to create the image, which will be named 'captured'.

Bitmap captured = new Bitmap(bounds.Width, bounds.Height, format);

Creating a GDI+ Drawing Surface

To use the GDI+ functions, a drawing surface must be created. The drawing surface is linked to the bitmap so that all activities performed affect the bitmap's contents. The Graphics class' FromImage method returns a correctly linked drawing surface.

Add the following line to create the drawing surface:

Graphics gdi = Graphics.FromImage(captured);

Copying the Screen Contents

With the GDI+ drawing surface prepared, the screen contents can now be captured. The Graphics class defines a method named CopyFromScreen for this purpose. The variant that we will use requires five parameters.

The first two parameters contain the co-ordinates of the top-left pixel to be captured. This can be found in the 'bounds' variable initialised earlier. The third and fourth parameters hold the co-ordinates in the drawing surface where we wish the copied image to be positioned. As we wish to copy to the top-left of the image, these are both set to zero. Setting a different pair of values allows the copying to be repositioned, cropping edges and creating margins. Finally, the size of the area to be copied is required. This again comes from the 'bounds' variable.

Add the following code to capture the screen and copy it into the Bitmap object.

gdi.CopyFromScreen(bounds.Left, bounds.Top, 0, 0, bounds.Size);

Displaying the Screen Grab

The process is now complete and the screenshot exists within the Bitmap object. All that remains is to display the image in the background of the form. To ensure that the width:height ratio of the image is preserved, the background will be set to 'zoom mode' before assigning the image.

To complete the program, add the following code. Then execute the application to see the results.

this.BackgroundImageLayout = ImageLayout.Zoom; this.BackgroundImage = captured;

No comments: