Lubo Blagoev's Blog

My thoughts on software and technology

Taking screenshots including the mouse pointer programmatically.


Recently I needed to take some screenshots of my screen that include the mouse pointer. While there are many programs out there that do just that I failed to find anything that is simple and easy enough and most importantly is free. There are some screen capture utilities that have free versions but non of them have the ability to include the mouse pointer or if they does this is included in the commercial version of the program.

After couple of hours evaluating I found the most promising solution -  Cropper by Brian Scott (http://www.codeplex.com/cropper). It is not only free but it is open source C# application. Unfortunately it can't capture the mouse pointer. At first I though it is difficult to do that since most of the tools include this in the commercial version. After some time investigating I saw one article from Zarko Gajic in Delphi About (http://delphi.about.com/od/graphics/l/aa021004a.htm). I used to read him a lot when I was doing Delphi development.

I had what I needed so I guess the next step was obvious. I modified the excellent Cropper tool to include the mouse pointer. The truth is that the tool is designed to be extensible enough and it is not much of a code so the task went very easy. I will post here the method that does the cursor capturing. The rest I intend to send to Brian Scott to include it in the next version of the tool. Here it is

The NativeMethods.cs contains additions for the needed interop signatures.

[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
    public Int32 cbSize;
    public Int32 flags;
    public IntPtr hCursor;
    public POINT ptScreenPos;
}

[DllImport("user32.dll")]
public static extern bool GetCursorInfo(out CURSORINFO pci);

public const Int32 CURSOR_SHOWING = 0x00000001;

[DllImport("user32.dll")]
public static extern IconHandle CopyIcon(IntPtr hIcon);

[StructLayout(LayoutKind.Sequential)]
public struct ICONINFO
{
    public bool fIcon;
    public Int32 xHotspot;
    public Int32 yHotspot;
    public IntPtr hbmMask;
    public IntPtr hbmColor;
}

[DllImport("user32.dll")]
public static extern bool GetIconInfo(IconHandle hIcon, out ICONINFO piconinfo);

[SuppressUnmanagedCodeSecurity, SecurityCritical,
DllImport("user32.dll", EntryPoint = "DestroyIcon", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);

[SecurityCritical, SuppressUnmanagedCodeSecurity,
DllImport("gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DeleteObject(IntPtr hObject);


[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public class IconHandle : SafeHandle
{
    [SecurityCritical, SecurityTreatAsSafe]
    public IconHandle()
        : base(IntPtr.Zero, true)
    {
    }

    [SecurityCritical]
    protected override bool ReleaseHandle()
    {
        return DestroyIcon(base.handle);
    }

    public override bool IsInvalid
    {
        [SecurityCritical, SecurityTreatAsSafe]
        get
        {
            return (base.handle == IntPtr.Zero);
        }
    }
}

And the additional method added to Fusion8.Cropper.Core.ImageCapture class.

private void AddCursor(Image image, Rectangle capturedRectangle)
{
    using (Graphics graphics = Graphics.FromImage(image))
    {
        NativeMethods.CURSORINFO cursorInfo;
        cursorInfo.cbSize = Marshal.SizeOf(typeof(NativeMethods.CURSORINFO));
        if (NativeMethods.GetCursorInfo(out cursorInfo))
        {
            if (cursorInfo.flags == NativeMethods.CURSOR_SHOWING)
            {
                using (NativeMethods.IconHandle handle = NativeMethods.CopyIcon(cursorInfo.hCursor))
                {
                    NativeMethods.ICONINFO iconInfo;
                    if (NativeMethods.GetIconInfo(handle, out iconInfo))
                    {
                        try
                        {
                            using (Icon icon = Icon.FromHandle(handle.DangerousGetHandle()))
                            {
                                int targetX = cursorInfo.ptScreenPos.X - capturedRectangle.X - iconInfo.xHotspot;
                                int targetY = cursorInfo.ptScreenPos.Y - capturedRectangle.Y - iconInfo.yHotspot;
                                graphics.DrawIcon(icon, targetX, targetY);
                            }
                        }
                        finally
                        {
                            NativeMethods.DeleteObject(iconInfo.hbmColor);
                            NativeMethods.DeleteObject(iconInfo.hbmMask);
                        }
                    }
                }
            }
        }
        else
        {
            Debug.WriteLine(Marshal.GetLastWin32Error());
        }
    }
}

Note the DeleteObject calls in the finally statement. I found couple of articles on the web that do not free these objects. You should avoid this mistake.

Here is the final result

 screenshot

Although I found the code quality not very good I think this is a great tool with all the functionality and the plug-ins. It does a wonderful job and is very user friendly. I will send my changes to Brian and hope you will like it. After all the biggest support you can give for an open source project is to use it.

Have fun!
Lubo

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

January 25. 2008 02:50 by Matt W

Nice Work. You forgot to mention a few things that you might have done. First, the first edit goes in the Fusion8.Cropper.Core.NativeMethods.cs class (not be confused with the other classes in various projects with the same name.) Also, you need to add a couple using statements to both of the above files.

Native methods:
using System.Security;
using System.Security.Permissions;

and ImageCapture:
using System.Runtime.InteropServices;

Finally, you never mentioned where to actually call the AddCursor method! You need to call it in theFusion8.Cropper.Core.ImageCapture.CaptureByHdc method right after the if statement, like so:

AddCursor(image, captureRectangle);

January 26. 2008 01:56 by lubo

Sure there is more to be said.
I have emailed the changes to Brian Scott and asked him to prepare a new version so anyone can use it.
Unfortunately Brian has not released a new version despite my request. Though I don't want to create two sources for Cropper, I guess I will post my changes here so anyone can download the source and the binaries.

April 2. 2008 21:54 by peter

irfanview is free and does it too.
you can choose under capture options if you want or not want to have mousepointer on the bitmap
Give up a folder location, set jpg compression and ctrl+F11 (when activated)
Every time you press then ctrl+F11 its automaticaly saved no previews just a beed that notifies you about it.
It's quite handy, for example log installation screens etc..

April 2. 2008 21:56 by peter

beed ahum i ment beep in previous comment.

Add comment


 

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]


Live preview

May 25. 2013 21:08 by