Monday, April 12, 2010

Screen Capture in WPF & WinForms Application

I was wondering how one can capture screenshot from an application. I saw many application was doing them. This thought made me interested to write an application that can take ScreenShot. In this article, I will demonstrate how one can take screenshot directly from the Desktop area using WinForms and WPF.


WinForms Application

In WinForms application, it is very easy to grab a screen snapshot. To do this you only need to create an object of Bitmap, on which you want to draw the image, get Graphics object from the Bitmap and then use CopyFromScreen method to actually draw the image into the Bitmap. To demonstrate let us consider this method :
public void CaptureScreen(double x, double y, double width, double height)
 {
            int ix, iy, iw, ih;
            ix = Convert.ToInt32(x);
            iy = Convert.ToInt32(y);
            iw = Convert.ToInt32(width);
            ih = Convert.ToInt32(height);
            Bitmap image = new Bitmap(iw, ih, 
                   System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            Graphics g = Graphics.FromImage(image);
            g.CopyFromScreen(ix, iy, ix, iy, 
                     new System.Drawing.Size(iw, ih), 
                     CopyPixelOperation.SourceCopy);
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.DefaultExt = "png";
            dlg.Filter = "Png Files|*.png";
            DialogResult res = dlg.ShowDialog();
            if (res == System.Windows.Forms.DialogResult.OK)
                image.Save(dlg.FileName, ImageFormat.Png);
 }

The above code takes Start X, Start Y, width and height and saves the image  into the disk. In this method, I created an object of Bitmap where I would be going to draw the image. There are large number of PixelFormat supported by the Bitmap object. You can choose anyone of them which suits you. As I am working on 32 Bit true color environment, I used Format32bppArgb.

After doing this, you need to actually draw into the Bitmap object. To do this, you need to get graphics object from the image. I used Graphics.FromImage(image) to grab the graphics object from the image, so that the graphics could draw into the Bitmap object.

Finally I called g.CopyFromScreen which actually captures the screen snapshot, just like what Screenshot does and writes on the Bitmap. The argument that I have passed determines the dimension of the image. The x, y, width and height determines where from the screen the snapshot should start and its width and height upto which it must go. At last, I used image.Save to save the image in Png format in the disk.

You should note, if you want transparency in your image, you should use PNG, as it supports transparency.


A little Depth


If you want to know what exactly happens in background, let us use DllImport to demonstrate the concept. Actually Screen capture is made using a API call to BitBlt. This method can be called with appropriate dimension just as the managed method CopyFromScreen, and get the image.
To implement using Native code :
internal class NativeMethods
{

    [DllImport("user32.dll")]
     public extern static IntPtr GetDesktopWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hwnd);
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    public static extern IntPtr GetForegroundWindow();
    [DllImport("gdi32.dll")]
    public static extern UInt64 BitBlt(IntPtr hDestDC, int x, int y, 
       int nWidth, int nHeight, IntPtr hSrcDC, 
       int xSrc, int ySrc, System.Int32 dwRop);

}

The NativeMethod BitBlt actually the most useful method in this context.  To grab the image using this you can use :
public void SaveScreen(double x, double y, double width, double height)
{
      int ix, iy, iw, ih;
      ix = Convert.ToInt32(x);
      iy = Convert.ToInt32(y);
      iw = Convert.ToInt32(width);
      ih = Convert.ToInt32(height);
      try
      {
          Bitmap myImage = new Bitmap(iw, ih);
          Graphics gr1 = Graphics.FromImage(myImage);
          IntPtr dc1 = gr1.GetHdc();
          IntPtr dc2 = NativeMethods.GetWindowDC(NativeMethods.GetForegroundWindow());
         NativeMethods.BitBlt(dc1, ix, iy, iw, ih, dc2, ix, iy, 13369376);
         gr1.ReleaseHdc(dc1);
         SaveFileDialog dlg = new SaveFileDialog();
         dlg.DefaultExt = "png";
         dlg.Filter = "Png Files|*.png";
         DialogResult res = dlg.ShowDialog();
         if (res == System.Windows.Forms.DialogResult.OK)
             myImage.Save(dlg.FileName, ImageFormat.Png);
       }
       catch { }
 }

In this function, I am doing the same thing that I did for the earlier. The difference is that, here I ma using NativeMethod to invoke the BitBlt directly rather than using the Managed code CopyFromScreen method.

If you want to capture the whole working area, you can use
Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
Screen.PrimaryScreen.Bounds.Width, and
Screen.PrimaryScreen.Bounds.Height
as its arguments.

Sample Application

In this sample application, you will find a WPF application that allows you to capture a part of the screen. Lets look how it works :

1. Run the application, you will be provided with a screen where you can drag mouse to take screenshot.
2. After you drag the mouse and Release the mouse, you will see a dialog box to save the image.
3. Finally after you save, you will see the snapshot in png format.

Thus it is very easy to work with Screen capture.

I hope you will like this application.

Download the Sample Application - ScreenShotSample.Zip(50KB)

3 comments:

  1. This is an excellent article, fantastic work. Just one question, I want to create a streaming app (similar to what remote desktop or remote assistance does). Would simply capturing the window contents repeatedly and straming be efiicient if done this way? If not, is there a standard way of capturing windows in a video format through dotNet?

    ReplyDelete
  2. See taking snap in streaming video will be a completely different approach. I will explain that in another article.

    For time being you can try
    http://www.vb-helper.com/howto_net_video_capture.html

    ReplyDelete
  3. thanks for the code , this helped me a lot in what I'm trying to do . there is just one very small thing which I think you have to fix in the code in captureScree() instead of :
    g.CopyFromScreen(ix, iy, ix, iy,
    new System.Drawing.Size(iw, ih), CopyPixelOperation.SourceCopy);

    you should put :

    g.CopyFromScreen(ix, iy, 0, 0,
    new System.Drawing.Size(iw, ih),
    CopyPixelOperation.SourceCopy);

    because we want to copy the captured graphics to (0,0) of the destination file .

    ReplyDelete

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.

Author's new book

Abhishek authored one of the best selling book of .NET. It covers ASP.NET, WPF, Windows 8, Threading, Memory Management, Internals, Visual Studio, HTML5, JQuery and many more...
Grab it now !!!