Click here to Skip to main content
15,881,204 members
Articles / Programming Languages / C#
Article

Image Capture

Rate me:
Please Sign up or sign in to vote.
4.78/5 (30 votes)
17 Feb 20048 min read 216.6K   11.4K   119   20
An article about image capture.

Sample Image

Introduction

The following subjects are discussed in this article:

  1. Capturing an image of the desktop or its work area.
  2. Capturing an image of a control or its client area.
  3. Capturing an image of what's underneath a control or its client area.
  4. Converting images from one format to another.
  5. Converting an image to usable icon.

Background

Several C# and C++ articles are available discussing desktop capture. This article generalizes from there and presents a testbed for loading, capturing, and saving images in various graphical formats including conversion of an image to a usable icon.

Captured images are specified areas of GDI windows such as the desktop or a GDI+ control. A GDI (Graphical Device Interface) window is specified by a window handle. To operate on a GDI+ object (basically a .NET control) using a GDI method, we need to use its GDI window handle. Fortunately, it is available.

C#
//  get a control's window handle for GDI manipulation
    ...
System.Windows.Forms.Control ctl = new System.Windows.Forms.Control();
System.IntPtr wndHandle = ctl.Handle;
    ...

Code Organization

The solution consists of two projects;

  1. A C# library containing the capture methods (Capture.cs), \ the interop methods (Dll.cs), as well as some debug and sound methods, and
  2. A testing form (TestCapture.cs) to exercise the capture methods.

Module Dll.cs

This module defines entry points to methods contained in various DLLs. To simplify access, a namespace called Dll is defined whose class names are the same as the DLLs to be accessed. Each class then defines the desired interop DLL entry points and equivalent C# methods.

Example class defining C# accessible entry points for a DLL

C#
    ...
/// <summary>
/// GDI32 dll access
/// </summary>
public class GDI32
{
  public const int SRCCOPY = 13369376;

  [DllImport("gdi32.dll", EntryPoint="DeleteDC")]
  public static extern IntPtr DeleteDC(IntPtr hDc);

  [DllImport("gdi32.dll", EntryPoint="DeleteObject")]
  public static extern IntPtr DeleteObject(IntPtr hDc);

  [DllImport("gdi32.dll", EntryPoint="BitBlt")]
  public static extern bool BitBlt(IntPtr hdcDest,int xDest,
             int yDest,int wDest,int hDest,IntPtr hdcSource,
             int xSrc,int ySrc,int RasterOp);

  [DllImport("gdi32.dll", EntryPoint="CreateCompatibleBitmap")]
  public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc,
             int nWidth, int nHeight);

  [DllImport("gdi32.dll", EntryPoint="CreateCompatibleDC")]
  public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

  [DllImport("gdi32.dll", EntryPoint="SelectObject")]
  public static extern IntPtr SelectObject(IntPtr hdc,IntPtr bmp);
}                                           // end class GDI32
    ...

Rather than directly accessing the interop methods from other projects, access in this solution is isolated to the classes in the C# library such as the Capture class.

C#
/// <SUMMARY>
/// Example of fully qualified Dll interop method access
/// </SUMMARY>
    ...
//    release window and capture resources
  Dll.USER32.ReleaseDC(wndHWND,wndHDC);  // release window context
  Dll.GDI32.DeleteDC(capHDC);            // delete capture context
  Dll.GDI32.DeleteObject(capBMP);        // delete capture bitmap
    ...

Module Capture.cs

GDI Window Capture

The underlying method used by other capture methods is shown below. This method captures an image within a specified rectangle within a specified window. The window is specified by a GDI window handle. The image is returned as a .NET bitmap.

The method operates as follows. First, a window device context is procured. Second, using the window device context, a compatible capture device context is created, and a GDI bitmap is created and associated with this capture device context. Third, the specified rectangle in the window device context is copied to the capture device context thus populating the GDI bitmap. Finally, the GDI bitmap is converted to a GDI+ bitmap and returned to the caller.

C#
/// <summary>
/// Captures the window or part thereof to a bitmap image.
/// </summary>
public static Bitmap Window(IntPtr wndHWND, 
             int x, int y, int width, int height)
{
  IntPtr  wndHDC = USER32.GetDC(wndHWND);
  // get context for window 

  //  create compatibile capture context and bitmap
  IntPtr  capHDC = GDI32.CreateCompatibleDC(wndHDC);
  IntPtr  capBMP = GDI32.CreateCompatibleBitmap(wndHDC, width, height);

  //  make sure bitmap non-zero
  if (capBMP == IntPtr.Zero)// if no compatible bitmap
  {
    USER32.ReleaseDC(wndHWND,wndHDC); //   release window context
    GDI32.DeleteDC(capHDC); //   delete capture context
    return null; //   return null bitmap
  }

  //  select compatible bitmap in compatible context
  //  copy window context to compatible context
  //  select previous bitmap back into compatible context
  IntPtr  prvHDC = (IntPtr)GDI32.SelectObject(capHDC,capBMP); 
  GDI32.BitBlt(capHDC,0,0,width,height,wndHDC,x,y,GDI32.SRCCOPY); 
  GDI32.SelectObject(capHDC,prvHDC);

  //  create GDI+ bitmap for window
  Bitmap  bmp = System.Drawing.Image.FromHbitmap(capBMP); 

  //  release window and capture resources
  USER32.ReleaseDC(wndHWND,wndHDC); // release window context
  GDI32.DeleteDC(capHDC); // delete capture context
  GDI32.DeleteObject(capBMP); // delete capture bitmap

  //  return bitmap image to user
  return bmp;  // return bitmap
}              // end method Window

Desktop Capture

The desktop capture methods (Desktop and DesktopWA) are shown below.

Method Desktop captures the entire desktop. It picks up the width and height of the desktop, a window handle for the desktop and then uses capture method Window to return a bitmap of the desktop. The width and height are procured using interop routines. This information can also be attained using the method System.Windows.Forms.Screen.PrimaryScreen.Bounds.

Method DesktopWA captures the working area of the desktop. It picks up the bounds of the desktop working area based on a specified control in that working area and then uses capture method Window to return a bitmap of the desktop.

C#
/// <summary>
/// Captures the desktop to a bitmap image
/// </summary>
public static Bitmap Desktop()
{
  // desktop width
  int    width = USER32.GetSystemMetrics(USER32.SM_CXSCREEN);
  // desktop height
  int    height = USER32.GetSystemMetrics(USER32.SM_CYSCREEN);
  // desktop window handle
  IntPtr desktopHWND = USER32.GetDesktopWindow();
  // return desktop bitmap
  return Window(desktopHWND,0,0,width,height);
} // end method Desktop

/// <summary>
/// Captures the desktop work area to a bitmap image
/// </summary>
public static Bitmap DesktopWA(Control ctl)
{
  // desktop work area
  Rectangle wa = Screen.GetWorkingArea(ctl);
  // desktop window handle          
  IntPtr    desktopHWND = USER32.GetDesktopWindow();
  // return work area bitmap
  return    Window(desktopHWND,wa.X,wa.Y,wa.Width,wa.Height);
} // end method DesktopWA

Control Capture

The control capture method is Control. The first method Control captures the entire control. The second method Control captures a specified portion of the control or what's underneath the control.

When capturing what's beneath a control, the desired area is converted to screen coordinates and a window handle to the screen (desktop) is utilized for the capture. The window handle for the control cannot be used since the desired area is on the screen under the control. To perform this capture and procure the desired bitmap, the control must be hidden prior to capture.

When capturing an area on the control, the desired area is converted to control coordinates and a window handle for the control is utilized for the capture.

C#
/// <summary>
/// Captures the control to a bitmap image. The entire control is
/// captured including both the client and non-client areas.
/// </summary>
public static Bitmap  Control(System.Windows.Forms.Control ctl)
{
  return Control(ctl,false,false); // capture entire control
} // end method Control

/// <summary>
/// Captures the specified area of the control or whats underneath
/// the control. If the argument flag client is true, only the client
/// area of the control is captured, otherwise the entire control is 
/// captured. If the argument flag under is true, the capture area under
/// the control is captured, otherwise the specified area on the control
/// is captured.
/// </summary>
public static Bitmap  Control(System.Windows.Forms.Control ctl
                             ,bool client,bool under)
{
  Bitmap    bmp;      // capture bitmap
  Rectangle ctlR;     // capture area in control coordinates
  Rectangle scrR;     // capture area in screen coordinates
  
  // get capture rectangle in control
  // coordinates and in screen coordinates
  if (client)        // if capturing client area
  {
     //   get rectangle in control coordinates
     ctlR = ctl.ClientRectangle;
     //   get rectangle in screen coordinates
     scrR = ctl.RectangleToScreen(ctlR);
  }
  else   // if capturing entire control
  {
    //   get rectangle in parent coordinates
    scrR = ctl.Bounds;
    if (ctl.Parent != null)  //   if parent exists
      //     map to screen coordinates
      scrR = ctl.Parent.RectangleToScreen(scrR);
    // get rectangle in control coordinates
    ctlR = ctl.RectangleToClient(scrR);
  }
  
  // capture an area under the control
  if (under)  // if capture area is under control
  {
    //   save control visibility
    bool prvV = ctl.Visible;
    if (prvV)   //   if control visible
    {
      //     make control invisible
      ctl.Visible = false;
      //     allow time for control to vanish
      Thread.Sleep(m_HDelay);
    ) //     prior to image capture

    //  Capture bitmap using desktop window handle and screen coordinates
    //  for the capture area. Note, the control window handle can NOT be used
    //  for capturing an area under the control.
    //   get window handle for desktop
    IntPtr  desktopHWND = USER32.GetDesktopWindow();
    //   get bitmap for capture area under control
    bmp = Window(desktopHWND,scrR);
    if (ctl.Visible != prvV) //   if control visibility was changed
      ctl.Visible = prvV;    //     restore previous visibility
  }

  //  capture an area on the control
  else  // if capture area not under control
  {
    //  Capture bitmap using control window handle and control coordinates
    //  for capture area.
    bmp = Window(ctl.Handle,ctlR);
    //   get bitmap using control window handle
  }
  return bmp;  // return requested bitmap
}              // end method Control

ImageCapture.cs

The image capture form allows images to be loaded, saved, captured, and converted. It handles one image at a time and does not as yet include image editing functions. It will, optionally, retain the aspect ratio of an image. Load and save support a number of image formats allowing image format conversion. And, images can be converted and saved as usable icons. The form was used for testing, and has very elemental error checking and recovery capabilities.

Image Capture

Action menu items control image capture. Desktop or desktop area image capture is accomplished by clicking the appropriate action menu item, "Capture Desktop" or "Capture Desktop Work Area" respectively.

Partial desktop image captures are done using the viewport. The viewport is the client area of the form. To use the viewport, it must first be opened. This is done by clicking action menu item "Open Viewport". Opening the viewport causes the form's transparency key to be set to the background color of the form. This makes the area underneath the client area of the form visible to the user. The user moves the form over and sizes the form to encompass the desired selection. Capture of the image is accomplished by clicking action menu item "Capture View". This causes the view to be captured and the viewport to be closed.

Prior to loading or capturing another image, the current image must be saved or released. The action menu item "Release View" will dispose off the current image.

Image Load

An image may be loaded from a file. For images other than icons, the method Image.FromFile is utilized to input the image. This method automatically detects the format of the image upon input, without requiring the user to specify that format. The image is then converted to a bitmap.

Icons are special cased. Icons are input using the Icon constructor, and then converted to a bitmap.

C#
/// <summary>
/// Menu selection to load an image from a file.
/// </summary>
private void miLoad_Click(object sender, System.EventArgs e)
{
  if (UnsavedBM())                         // if unsaved image exists
    return;                                //   return
  CloseViewport();                         // close viewport

  DialogResult dr = ofd.ShowDialog(this);  // show the dialog
  if (dr != DialogResult.OK)               // if not ok
    return;                                //   forget it

  string fn = ofd.FileName;                // pick up file path
  int    idx = fn.LastIndexOf("\\");       // get last backslash
  sfd.InitialDirectory = (idx <= 0)? "" : fn.Substring(0,idx);
  sfd.FileName = fn;                       // make default for save
  Dispose(capBM);              // dispose of previous bitmap (if any)
    
  idx = fn.LastIndexOf(".") + 1;           // find start of extension
  string ext = (idx > 0) && (idx < fn.Length)? fn.Substring(idx) : "";
  if (ext.ToLower().Equals("ico"))         // if file is an icon
  {
    this.Icon = new Icon(fn);              //   read new form icon from file
    capBM = this.Icon.ToBitmap();          //   convert to bitmap
  }
  else                                     // if file not an icon
    capBM = new Bitmap(Image.FromFile(fn));//   read bitmap from file

  PicInCtl(curCtl,false);   // place new image in current control
  capBMSaved = true;        // image has been saved, it came from a file
  Play("LoadView.wav");     // play load view sound
}                           // end action miLoad_Click

Image Display

A loaded or captured image is manipulated internally as a Bitmap. It should be noted that a Bitmap is based on an Image and can thus be thought of as an image when necessary. Essentially, the current image is displayed as the background of the client area of the form. When the form is resized, the image is resized to fit within its client area. If the image aspect ratio is being preserved, the normal case, then an aspect sized version of the image will be fit within the client area of the form. No image significance is lost since the original bitmap is preserved.

Image Save

An image may be saved to a file. For images other than icons, the Bitmap instance method Save is utilized to output the image. This method supports a large number of image formats. However, the image format must be explicitly specified when invoking Bitmap instance method Save. A filter is used in the save file dialog to allow the user to select the image format.

C#
/// <summary>
/// Menu selection to save the image to a file.
/// </summary>
private void miSave_Click(object sender, System.EventArgs e)
{
  if (capBM == null)                       // if no bitmap
    return;                                //   return
  DialogResult dr = sfd.ShowDialog(this);  // show the dialog
  if (dr != DialogResult.OK)               // if not ok
    return;                                //   forget it

  string fn = sfd.FileName;                // pick up file path
  if (fn.Equals(""))                       // if no filename
  {
    Play("Error.wav");                     //   play nothing saved sound
    MessageBox.Show("No filename specified, nothing saved");
    return;
  }

  // set default image type to selected filter format
  ImageType it = ImageType.SetImageType(sfd.FilterIndex-1);  
  // filter index is one based
  
  if (it.format == ImageFormat.Icon)     // if saving an icon
  {
    Icon = BitmapToIcon(capBM,aspect);   //   convert bitmap to icon
    Stream s = sfd.OpenFile();           //   open file
    Icon.Save(s);                        //   save the icon
    s.Close();                           //   close file
  }
  else                                   // if saving other format
    capBM.Save(fn,it.format);            //   use generic image save
  capBMSaved = true;                     // image has been saved
  Play("SaveView.wav");                  // play image saved sound
}                                        // end action miSave_Click

Image to Icon

An image may be saved as an icon. A usable icon is based on a small bitmap. The maximum size of this bitmap is determined by display resolution and color support. For the purposes of this demo, a display supporting at least 256 colors or more was assumed. For this display assumption, the maximum bitmap size is 96 by 96. Converting the image bitmap to a bitmap for the icon is not a problem. However, preserving the image aspect ratio is a problem.

The choice made in this demo for icon aspect preservation was to drop significance in the longest direction. In other words, a bitmap was formed that was 96 pixels in the shortest direction and more than this in the longest direction. The icon bitmap was then extracted from this bitmap using a centering rectangle thus dropping some significance along both outer edges for the longest direction. To preserve aspect without losing significance, the image should be edited and/or cropped into a square. This can be done in an external image editor since these features don't currently exist in ImageCapture.

C#
/// <summary>
/// Convert bitmap to icon preserving aspect if requested
/// </summary>
private Icon BitmapToIcon(Bitmap obm,bool preserve)
{
  Bitmap bm;
  //  if not preserving aspect
  if (!preserve)                         // if not preserving aspect
    bm = new Bitmap(obm,ICON_W,ICON_H);  //   rescale from original bitmap

  //  if preserving aspect drop excess
  //  significance in least significant direction
  else                           // if preserving aspect
  {
    Rectangle rc = new Rectangle(0,0,ICON_W,ICON_H);
    if (obm.Width >= obm.Height)  //   if width least significant
    {                            //  rescale width based on max icon height
      bm = new Bitmap(obm,(ICON_H*obm.Width)/obm.Height,ICON_H);
      rc.X = (bm.Width - ICON_W) / 2; // chop off excess width significance
      if (rc.X < 0) rc.X = 0;
    }
    else                   //   if height least significant
    {                      //   rescale height based on max icon width
      bm = new Bitmap(obm,ICON_W,(ICON_W*obm.Height)/obm.Width);
      rc.Y = (bm.Height - ICON_H) / 2; // chop off excess height significance
      if (rc.Y < 0) rc.Y = 0;
    }
    bm = bm.Clone(rc,bm.PixelFormat); //   bitmap for icon rectangle
  }

  //  create icon from bitmap
  Icon icon = Icon.FromHandle(bm.GetHicon()); // create icon from bitmap
  bm.Dispose();                               // dispose of bitmap
  return icon;                                // return icon
}                                             // end method BitmapToIcon

Points of Interest

  1. Aspect Preservation - Abandoned Approach. Keying off form size changes to then modify the client area of the form for aspect preservation created an out of order resonating series of form size changes that led to unfortunate behavior. Even though these issues were eventually overcome, the approach was abandoned as too cumbersome.
  2. GDI Window Handles - Since a GDI window handle can be used to conger up the associated GDI+ control, using the Control.FromHandle method, it seemed possible there might be a GDI+ control available for the desktop that could be accessed using this method. Of course, the results after testing this hypothesis were disappointing. Although the test crashes were variously reported by the debugger, no GDI+ control for the desktop was ever forthcoming using this technique.
  3. Image Beneath Control - Various methods were tested to gather the image beneath a control. Timing issues arose during some of the early tests. A variable delay was introduced to overcome this problem. The code still has a 5ms delay to allow the control being hidden to drop out of the desktop bitmap. This delay may no longer be required. More testing needs to be done in this regard.

History

01/31/2004 - Initial release.

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


Written By
Web Developer
United States United States
Don is a consultant specializing in advanced tools and software for embedded applications.

Comments and Discussions

 
GeneralHere is capturing with a managed code Pin
Kam16-Jul-08 7:18
Kam16-Jul-08 7:18 
GeneralAnother Simple Way Pin
Anil Kumar K G16-Apr-08 16:28
Anil Kumar K G16-Apr-08 16:28 
QuestionI need the help please in the following Pin
Ali Habib23-Nov-07 7:11
Ali Habib23-Nov-07 7:11 
Generalpoor icon Pin
Muammar©26-May-07 21:36
Muammar©26-May-07 21:36 
GeneralRe: poor icon Pin
GenghisDon12-Jun-07 13:52
GenghisDon12-Jun-07 13:52 
Generalhidden windows Pin
kajok3-Oct-06 6:13
kajok3-Oct-06 6:13 
GeneralRe: hidden windows Pin
GenghisDon4-Oct-06 5:21
GenghisDon4-Oct-06 5:21 
GeneralRe: hidden windows Pin
domini_harling27-Oct-06 9:10
domini_harling27-Oct-06 9:10 
GeneralRe: hidden windows Pin
Melatonin1-Jan-07 0:05
Melatonin1-Jan-07 0:05 
GeneralRe: hidden windows Pin
Pavel Rubanov5-Feb-07 5:47
Pavel Rubanov5-Feb-07 5:47 
GeneralRe: hidden windows Pin
lalitkale18-May-07 0:28
lalitkale18-May-07 0:28 
GeneralCapturing Image of Minimized Window Pin
IslamianFalcon30-Nov-05 1:32
IslamianFalcon30-Nov-05 1:32 
GeneralRe: Capturing Image of Minimized Window Pin
dmihailescu15-Jun-07 7:14
dmihailescu15-Jun-07 7:14 
QuestionCapture of video image Pin
19-Oct-05 3:40
suss19-Oct-05 3:40 
AnswerRe: Capture of video image Pin
photonics10-Jan-06 3:58
photonics10-Jan-06 3:58 
Generall256 color icons Pin
Qruk19-Aug-05 4:06
Qruk19-Aug-05 4:06 
Generalmanaged code Pin
shinobumono12-Aug-05 5:31
shinobumono12-Aug-05 5:31 
GeneralSaving GDI control as image. Pin
jay.gosavi25-Jul-05 0:39
jay.gosavi25-Jul-05 0:39 
GeneralControl Capture Pin
2-Jun-05 22:01
suss2-Jun-05 22:01 
GeneralConvert bitmap from 24bit color to 256 color Pin
MDM_RVS24-Apr-04 14:28
MDM_RVS24-Apr-04 14:28 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.