Get a screenshot of a specific application
Asked Answered
W

4

59

I know I can get the screenshot of the entire screen using Graphics.CopyFromScreen(). However, what if I just want the screenshot of a specific application?

Wanigan answered 21/5, 2009 at 3:57 Comment(0)
P
38

Here's some code to get you started:

public void CaptureApplication(string procName)
{
    var proc = Process.GetProcessesByName(procName)[0];
    var rect = new User32.Rect();
    User32.GetWindowRect(proc.MainWindowHandle, ref rect);

    int width = rect.right - rect.left;
    int height = rect.bottom - rect.top;

    var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
    using (Graphics graphics = Graphics.FromImage(bmp))
    {
        graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
    }

    bmp.Save("c:\\tmp\\test.png", ImageFormat.Png);
}

private class User32
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Rect
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
}

It works, but needs improvement:

  • You may want to use a different mechanism to get the process handle (or at least do some defensive coding)
  • If your target window isn't in the foreground, you'll end up with a screenshot that's the right size/position, but will just be filled with whatever is in the foreground (you probably want to pull the given window into the foreground first)
  • You probably want to do something other than just save the bmp to a temp directory
Playback answered 21/5, 2009 at 5:24 Comment(6)
this doesn't bring the window for the selected process to the foreground in win7, so you will get a screenshot with the active windowFulvous
@alconja i used your code to take a snapshot of notepad .but it took a snapshot of visual studio which was the active window .can we use this to take a picture of none active window ?Lourdeslourie
@FastSnail - I was going to suggest you try the other answer, but I see from your comments that that isn't working either... Another option might be to try to find a pinvoke method that pulls the target app/window into the foreground first. For example, SwitchToThisWindow, maybe...Playback
Does it work if the window is hidden or minimized? CopyScreen would create exception if screen is locked right?Pollination
@Pollination - it's been a while, but no I think this one just takes a screenshot of a region of the screen, so won't work if the window isn't visible. The answer below claims to work in that instance (not sure about locked screens though, sorry).Playback
for opengl process this is not working, any suggestions?Brigitta
A
128

If you want to print form screen you can use

PrintWindow(Handle);

The PrintWindow win32 api will capture a window bitmap even if the window is covered by other windows or if it is off screen:

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);

public static Bitmap PrintWindow(IntPtr hwnd)    
{       
    RECT rc;        
    GetWindowRect(hwnd, out rc);
   
    Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);        
    Graphics gfxBmp = Graphics.FromImage(bmp);        
    IntPtr hdcBitmap = gfxBmp.GetHdc();        

    PrintWindow(hwnd, hdcBitmap, 0);  
  
    gfxBmp.ReleaseHdc(hdcBitmap);               
    gfxBmp.Dispose(); 
   
    return bmp;   
}

The reference to RECT above can be resolved with the following class:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    private int _Left;
    private int _Top;
    private int _Right;
    private int _Bottom;

    public RECT(RECT Rectangle) : this(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom)
    {
    }
    public RECT(int Left, int Top, int Right, int Bottom)
    {
        _Left = Left;
        _Top = Top;
        _Right = Right;
        _Bottom = Bottom;
    }

    public int X {
        get { return _Left; }
        set { _Left = value; }
    }
    public int Y {
        get { return _Top; }
        set { _Top = value; }
    }
    public int Left {
        get { return _Left; }
        set { _Left = value; }
    }
    public int Top {
        get { return _Top; }
        set { _Top = value; }
    }
    public int Right {
        get { return _Right; }
        set { _Right = value; }
    }
    public int Bottom {
        get { return _Bottom; }
        set { _Bottom = value; }
    }
    public int Height {
        get { return _Bottom - _Top; }
        set { _Bottom = value + _Top; }
    }
    public int Width {
        get { return _Right - _Left; }
        set { _Right = value + _Left; }
    }
    public Point Location {
        get { return new Point(Left, Top); }
        set {
            _Left = value.X;
            _Top = value.Y;
        }
    }
    public Size Size {
        get { return new Size(Width, Height); }
        set {
            _Right = value.Width + _Left;
            _Bottom = value.Height + _Top;
        }
    }

    public static implicit operator Rectangle(RECT Rectangle)
    {
        return new Rectangle(Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height);
    }
    public static implicit operator RECT(Rectangle Rectangle)
    {
        return new RECT(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom);
    }
    public static bool operator ==(RECT Rectangle1, RECT Rectangle2)
    {
        return Rectangle1.Equals(Rectangle2);
    }
    public static bool operator !=(RECT Rectangle1, RECT Rectangle2)
    {
        return !Rectangle1.Equals(Rectangle2);
    }

    public override string ToString()
    {
        return "{Left: " + _Left + "; " + "Top: " + _Top + "; Right: " + _Right + "; Bottom: " + _Bottom + "}";
    }

    public override int GetHashCode()
    {
        return ToString().GetHashCode();
    }

    public bool Equals(RECT Rectangle)
    {
        return Rectangle.Left == _Left && Rectangle.Top == _Top && Rectangle.Right == _Right && Rectangle.Bottom == _Bottom;
    }

    public override bool Equals(object Object)
    {
        if (Object is RECT) {
            return Equals((RECT)Object);
        } else if (Object is Rectangle) {
            return Equals(new RECT((Rectangle)Object));
        }

        return false;
    }
}
Alvertaalves answered 26/5, 2009 at 15:15 Comment(16)
Great solution. I just want to point out that sometimes PixelFormat.Format32bppArgb gives white artifacts. In that case just try to use some other format instead, such as PixelFormat.Format24bppRgbCristie
not if its minimized though, also it makes the window you are targeting flicker when you PrintWindow()Orme
how do i use this method? PrintWindow(what to pass here)Dorso
The HWND of the top level window you would like to get an image ofAlvertaalves
how can I get a HWND?Tang
@MauriceFlanagan i'm getting entirely black image ?why??Lourdeslourie
@FastSnail Probably because it's a DirectX window, I think there is some way to capture them on Windows 10 but haven't looked into itAlvertaalves
Thanks for this. Just in case somebody else had to look it up, The referencefor DLLimport is using System.Runtime.InteropServices;Rosemari
@FastSnail I had the same issue. I guess you are starting a new process and want to capture that. The problem is, that your window is not initialized yet. This is the case even if you call Process.WaitForIdle(). I am a simple man and wrapped the extern call to GetWindowRect(hwnd, out rc); into a do-while loop, with rc.Width == 0 || rc.Height == 0 as the exit condition.Peritoneum
Has the black image solution been added to this solution? I'm running into the same thing on Windows 10Citarella
@Seva To get the HWND use this this.HandleCovalence
Always a black bitmap for UWP apps. I can't even seem to get DWM thumbnails for a UWP app while I'm on the same desktop as the UWP app. However, I can get the DWM thumbnail for a UWP app when I'm not on the same desktop as it. It's driving me crazy.Petrapetracca
What is the cause when the result of picture is black?Recrudescence
@CodeGuru: for non-UWP apps, likely the window is not visible. Check out GetWindowLongPtr with GWL_STYLE and compare against WS_VISIBLEDonohoe
I found a solution to the black image problem. Try putting in the value of 2 as the 3rd parameter (flags) for PrintWindow. After some digging I found the following in the chromium source: "The PW_RENDERFULLCONTENT flag is undocumented, but works starting in Windows 8.1. It allows for capturing the contents of the window that are drawn using DirectComposition." UINT flags = PW_CLIENTONLY | PW_RENDERFULLCONTENT; and so I do: int PW_CLIENTONLY = 0x1; int PW_RENDERFULLCONTENT = 0x2; PrintWindow(hwnd, hdcBitmap, PW_CLIENTONLY | PW_RENDERFULLCONTENT);Masquerade
Thank you jargon. I was so happy I found your comment, help me out immensely. I was worried I would need to start using directx to capture the screen and rewrite everything.Theorize
P
38

Here's some code to get you started:

public void CaptureApplication(string procName)
{
    var proc = Process.GetProcessesByName(procName)[0];
    var rect = new User32.Rect();
    User32.GetWindowRect(proc.MainWindowHandle, ref rect);

    int width = rect.right - rect.left;
    int height = rect.bottom - rect.top;

    var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
    using (Graphics graphics = Graphics.FromImage(bmp))
    {
        graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
    }

    bmp.Save("c:\\tmp\\test.png", ImageFormat.Png);
}

private class User32
{
    [StructLayout(LayoutKind.Sequential)]
    public struct Rect
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
}

It works, but needs improvement:

  • You may want to use a different mechanism to get the process handle (or at least do some defensive coding)
  • If your target window isn't in the foreground, you'll end up with a screenshot that's the right size/position, but will just be filled with whatever is in the foreground (you probably want to pull the given window into the foreground first)
  • You probably want to do something other than just save the bmp to a temp directory
Playback answered 21/5, 2009 at 5:24 Comment(6)
this doesn't bring the window for the selected process to the foreground in win7, so you will get a screenshot with the active windowFulvous
@alconja i used your code to take a snapshot of notepad .but it took a snapshot of visual studio which was the active window .can we use this to take a picture of none active window ?Lourdeslourie
@FastSnail - I was going to suggest you try the other answer, but I see from your comments that that isn't working either... Another option might be to try to find a pinvoke method that pulls the target app/window into the foreground first. For example, SwitchToThisWindow, maybe...Playback
Does it work if the window is hidden or minimized? CopyScreen would create exception if screen is locked right?Pollination
@Pollination - it's been a while, but no I think this one just takes a screenshot of a region of the screen, so won't work if the window isn't visible. The answer below claims to work in that instance (not sure about locked screens though, sorry).Playback
for opengl process this is not working, any suggestions?Brigitta
W
9

Based on Alconja's answer, I made a few improvements:

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hWnd);

private const int SW_RESTORE = 9;

[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);

[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);

public Bitmap CaptureApplication(string procName)
{
    Process proc;

    // Cater for cases when the process can't be located.
    try
    {
        proc = Process.GetProcessesByName(procName)[0];
    }
    catch (IndexOutOfRangeException e)
    {
        return null;
    }

    // You need to focus on the application
    SetForegroundWindow(proc.MainWindowHandle);
    ShowWindow(proc.MainWindowHandle, SW_RESTORE);

    // You need some amount of delay, but 1 second may be overkill
    Thread.Sleep(1000);

    Rect rect = new Rect();
    IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);

    // sometimes it gives error.
    while (error == (IntPtr)0)
    {
        error = GetWindowRect(proc.MainWindowHandle, ref rect);
    }

    int width = rect.right - rect.left;
    int height = rect.bottom - rect.top;

    Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
    Graphics.FromImage(bmp).CopyFromScreen(rect.left,
                                           rect.top,
                                           0,
                                           0,
                                           new Size(width, height),
                                           CopyPixelOperation.SourceCopy);

    return bmp;
}
Wanigan answered 21/5, 2009 at 6:54 Comment(3)
after skimming through this i noticed that, if the process closes during the Thread.Sleep(1000); you will have an infinite loop.Orme
@NicolasTyler is right. To clarify, the issue is that calling GetWindowRect with an HWND that is no longer valid will always return zero, meaning that the while loop in this answer will never exit and just burn CPU forever, which is a pretty serious bug. But otherwise I think this answer is an elegant solution. Maybe just limit to a fixed number of retries, and maybe sleeping a little in between. Or don't retry in this method.Ona
This answer also doesn't dispose the Graphics object.Ona
S
1

You could look into P/Invoking the win32 way of doing this, an article to this effect... sort of.

Basically, go through the trouble of setting up a DC to a bitmap and send WM_PRINT to the application window in question. Its pretty nasty, all told, but may work for you.

Functions you may need: SendMessage, GetDC, CreateCompatibleBitmp, and SelectObject.

I can't say I've ever done this before, but this is how I'd attack the problem. (Well, I'd probably do it in pure C but still; roughly the way I'd attack it).

Shavonneshaw answered 21/5, 2009 at 4:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.