Cyotek Development Bloghttps://devblog.cyotek.com/tag/screenshot/atom.xml2019-02-25T16:31:04ZCapturing screenshots using C# and p/invokeurn:uuid:758976f2-dbe8-449a-80ef-1fc869e8d5fb2019-02-25T16:31:04Z2017-08-27T13:40:29Z<p>I was recently updating some documentation and wanted to
programmatically capture some screenshots of the application in
different states. This article describes how you can easily
capture screenshots in your own applications.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1c.png" class="gallery" title="Capturing a screenshot of the desktop" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1c.png" alt="Capturing a screenshot of the desktop" decoding="async" loading="lazy" /></a><figcaption>Capturing a screenshot of the desktop</figcaption></figure><h2 id="using-the-win32-api">Using the Win32 API</h2>
<p>This article makes use of a number of Win32 API methods.
Although you may not have much call to use them directly in day
to day .NET (not to mention Microsoft wanting everyone to use
universal &quot;apps&quot; these days), they are still extraordinarily
useful and powerful.</p>
<p>This article does assume you know the basics of platform invoke
so I won't cover it here. In regards to the actual API's I'm
using, you can find lots of information about them either on
<a href="https://msdn.microsoft.com/en-us/library/ee663300(v=vs.85).aspx" rel="external nofollow noopener">MSDN</a>, or <a href="http://www.pinvoke.net/" rel="external nofollow noopener">PInvoke.net</a>.</p>
<blockquote>
<p>A number of the API's used in this article are GDI calls.
Generally, when you're using the Win32 GDI API, you need to do
things in pairs. If something is created (pens, brushes,
bitmaps, icons etc.), then it usually needs to be explicitly
destroyed when finished with (there are some exceptions just
to keep you on your toes). Although there haven't been GDI
limits in Windows for some time now (as far as I know!), it's
still good not to introduce memory leaks. In addition, device
contexts always have a number of objects associated with them.
If you assign a new object to a context, you must restore the
original object when you're done. I'm a little rusty with this
so hopefully I'm not missing anything out.</p>
</blockquote>
<h2 id="setting-up-a-device-context-for-use-with-bitblt">Setting up a device context for use with BitBlt</h2>
<p>To capture a screenshot, I'm going to be using the <code>BitBlt</code> API.
This copies information from one device context to another,
meaning I'm going to need a source and destination context to
process.</p>
<p>The source is going to be the desktop, so first I'll use the
<code>GetDesktopWindow</code> and <code>GetWindowDC</code> calls to obtain this. As
calling <code>GetWindowDC</code> essentially places a lock on it, I also
need to release it when I'm finished with it.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
IntPtr desktophWnd <span class="symbol">=</span> GetDesktopWindow<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
IntPtr desktopDc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span>desktophWnd<span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// TODO</span>

ReleaseDC<span class="symbol">(</span>desktophWnd<span class="symbol">,</span> desktopDc<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>Now for the destination - for this, I'm going to create a memory
context using <code>CreateCompatibleDC</code>. When you call this API, you
pass in an existing DC and the new one will be created based on
that.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
IntPtr memoryDc <span class="symbol">=</span> CreateCompatibleDC<span class="symbol">(</span>desktopDc<span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// TODO</span>

DeleteDC<span class="symbol">(</span>memoryDc<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>There's still one last step to perform - by itself, that memory
DC isn't hugely useful. We need to create and assign a GDI
bitmap to it. To do this, first create a bitmap using
<code>CreateCompatibleBitmap</code> and then attach it to the DC using
<code>SelectObject</code>. <code>SelectObject</code> will also return the relevant old
object which we need to restore (again using <code>SelectObject</code>)
when we're done. We also use <code>DeleteObject</code> to clean up the
bitmap.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
IntPtr bitmap <span class="symbol">=</span> CreateCompatibleBitmap<span class="symbol">(</span>desktopDc<span class="symbol">,</span> width<span class="symbol">,</span> height<span class="symbol">)</span><span class="symbol">;</span>
IntPtr oldBitmap <span class="symbol">=</span> SelectObject<span class="symbol">(</span>memoryDc<span class="symbol">,</span> bitmap<span class="symbol">)</span><span class="symbol">;</span>

<span class="comment">// TODO</span>

SelectObject<span class="symbol">(</span>memoryDc<span class="symbol">,</span> oldBitmap<span class="symbol">)</span><span class="symbol">;</span>
DeleteObject<span class="symbol">(</span>bitmap<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>Although this might seem like a lot of effort, it's not all that
different from using objects implementing <code>IDisposable</code> in C#,
just C# makes it a little easier with things like the <code>using</code>
statement.</p>
<h2 id="calling-bitblt-to-capture-a-screenshot">Calling BitBlt to capture a screenshot</h2>
<p>With the above setup out the way, we have a device context which
provides access to a bitmap of the desktop, and we have a new
device context ready to transfer data to. All that's left to do
is make the <code>BitBlt</code> call.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">const</span> <span class="keyword">int</span> SRCCOPY <span class="symbol">=</span> <span class="number">0x00CC0020</span><span class="symbol">;</span>
<span class="keyword">const</span> <span class="keyword">int</span> CAPTUREBLT <span class="symbol">=</span> <span class="number">0x40000000</span><span class="symbol">;</span>

<span class="keyword">bool</span> success <span class="symbol">=</span> BitBlt<span class="symbol">(</span>memoryDc<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> width<span class="symbol">,</span> height<span class="symbol">,</span> desktopDc<span class="symbol">,</span> left<span class="symbol">,</span> top<span class="symbol">,</span> SRCCOPY <span class="symbol">|</span> CAPTUREBLT<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>success<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> Win<span class="number">32</span>Exception<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>If you've ever used the <code>DrawImage</code> method of a <code>Graphics</code>
object before, this call should be fairly familiar - we pass in
the DC to write too, along with the upper left corner where data
will be copied (<code>0, 0</code> in this example), followed by the <code>width</code>
and <code>height</code> of the rectangle - this applies to both the source
and destination. Finally, we pass in the source device context,
and the upper left corner where data will be copied from, along
with flags that detail how the data will be copied.</p>
<p>In my old VB6 days, I would just use <code>SRCCOPY</code> (direct copy),
but in those days windows were simpler things. The <code>CAPTUREBLT</code>
flag ensures the call works properly with layered windows.</p>
<p>If the call fails, I throw a new <code>Win32Exception</code> object without
any parameters - this will take care of looking up the result
code for the <code>BitBlt</code> failure and filling in an appropriate
message.</p>
<p>Now that our destination bitmap has been happily &quot;painted&quot; with
the specified region from the desktop we need to get it into
.NET-land. We can do this via the <code>FromHbitmap</code> static method of
the <code>Image</code> class - this method accepts a GDI bitmap handle and
return a fully fledged .NET <code>Bitmap</code> object from it.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
Bitmap result <span class="symbol">=</span> Image<span class="symbol">.</span>FromHbitmap<span class="symbol">(</span>bitmap<span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>As the above code is piecemeal, the following helper method will
accept a <code>Rectangle</code> which describes which part of the desktop
you want to capture and will then return a <code>Bitmap</code> object
containing the captured information.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> BitBlt<span class="symbol">(</span>IntPtr hdcDest<span class="symbol">,</span> <span class="keyword">int</span> nxDest<span class="symbol">,</span> <span class="keyword">int</span> nyDest<span class="symbol">,</span> <span class="keyword">int</span> nWidth<span class="symbol">,</span> <span class="keyword">int</span> nHeight<span class="symbol">,</span> IntPtr hdcSrc<span class="symbol">,</span> <span class="keyword">int</span> nXSrc<span class="symbol">,</span> <span class="keyword">int</span> nYSrc<span class="symbol">,</span> <span class="keyword">int</span> dwRop<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr CreateCompatibleBitmap<span class="symbol">(</span>IntPtr hdc<span class="symbol">,</span> <span class="keyword">int</span> width<span class="symbol">,</span> <span class="keyword">int</span> nHeight<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr CreateCompatibleDC<span class="symbol">(</span>IntPtr hdc<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr DeleteDC<span class="symbol">(</span>IntPtr hdc<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr DeleteObject<span class="symbol">(</span>IntPtr hObject<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr GetDesktopWindow<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr GetWindowDC<span class="symbol">(</span>IntPtr hWnd<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> ReleaseDC<span class="symbol">(</span>IntPtr hWnd<span class="symbol">,</span> IntPtr hDc<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;gdi32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr SelectObject<span class="symbol">(</span>IntPtr hdc<span class="symbol">,</span> IntPtr hObject<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">const</span> <span class="keyword">int</span> SRCCOPY <span class="symbol">=</span> <span class="number">0x00CC0020</span><span class="symbol">;</span>

<span class="keyword">const</span> <span class="keyword">int</span> CAPTUREBLT <span class="symbol">=</span> <span class="number">0x40000000</span><span class="symbol">;</span>

<span class="keyword">public</span> Bitmap CaptureRegion<span class="symbol">(</span>Rectangle region<span class="symbol">)</span>
<span class="symbol">{</span>
 IntPtr desktophWnd<span class="symbol">;</span>
 IntPtr desktopDc<span class="symbol">;</span>
 IntPtr memoryDc<span class="symbol">;</span>
 IntPtr bitmap<span class="symbol">;</span>
 IntPtr oldBitmap<span class="symbol">;</span>
 <span class="keyword">bool</span> success<span class="symbol">;</span>
 Bitmap result<span class="symbol">;</span>

 desktophWnd <span class="symbol">=</span> GetDesktopWindow<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 desktopDc <span class="symbol">=</span> GetWindowDC<span class="symbol">(</span>desktophWnd<span class="symbol">)</span><span class="symbol">;</span>
 memoryDc <span class="symbol">=</span> CreateCompatibleDC<span class="symbol">(</span>desktopDc<span class="symbol">)</span><span class="symbol">;</span>
 bitmap <span class="symbol">=</span> CreateCompatibleBitmap<span class="symbol">(</span>desktopDc<span class="symbol">,</span> region<span class="symbol">.</span>Width<span class="symbol">,</span> region<span class="symbol">.</span>Height<span class="symbol">)</span><span class="symbol">;</span>
 oldBitmap <span class="symbol">=</span> SelectObject<span class="symbol">(</span>memoryDc<span class="symbol">,</span> bitmap<span class="symbol">)</span><span class="symbol">;</span>

 success <span class="symbol">=</span> BitBlt<span class="symbol">(</span>memoryDc<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> region<span class="symbol">.</span>Width<span class="symbol">,</span> region<span class="symbol">.</span>Height<span class="symbol">,</span> desktopDc<span class="symbol">,</span> region<span class="symbol">.</span>Left<span class="symbol">,</span> region<span class="symbol">.</span>Top<span class="symbol">,</span> SRCCOPY <span class="symbol">|</span> CAPTUREBLT<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">try</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>success<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> Win<span class="number">32</span>Exception<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 result <span class="symbol">=</span> Image<span class="symbol">.</span>FromHbitmap<span class="symbol">(</span>bitmap<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">finally</span>
 <span class="symbol">{</span>
 SelectObject<span class="symbol">(</span>memoryDc<span class="symbol">,</span> oldBitmap<span class="symbol">)</span><span class="symbol">;</span>
 DeleteObject<span class="symbol">(</span>bitmap<span class="symbol">)</span><span class="symbol">;</span>
 DeleteDC<span class="symbol">(</span>memoryDc<span class="symbol">)</span><span class="symbol">;</span>
 ReleaseDC<span class="symbol">(</span>desktophWnd<span class="symbol">,</span> desktopDc<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> result<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Note the <code>try ... finally</code> block used to try and free GDI
resources if the <code>BitBlt</code> or <code>FromHbitmap</code> calls fail. Also
note how the clean-up is the exact reverse of
creation/selection.</p>
</blockquote>
<p>Now that we have this method, we can use it in various ways as
demonstrated below.</p>
<h2 id="capturing-a-single-window">Capturing a single window</h2>
<p>If you want to capture a window in your application, you could
call <code>Capture</code> with the value of the <code>Bounds</code> property of your
<code>Form</code>. But if you want to capture an external window then
you're going to need to go back to the Win32 API. The
<code>GetWindowRect</code> function will return any window's boundaries.</p>
<p>Win32 has its own version of .NET's <code>Rectangle</code> structure, named
<code>RECT</code>. This differs slightly from the .NET version in that it
has <code>right</code> and <code>bottom</code> properties, not <code>width</code> and <code>height</code>.
The <code>Rectangle</code> class has a helper method, <code>FromLTRB</code> which
constructs a <code>Rectangle</code> from left, top, right and bottom
properties which means you don't need to perform the subtraction
yourself.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1a.png" class="gallery" title="Capturing a screenshot of a single window" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1a.png" alt="Capturing a screenshot of a single window" decoding="async" loading="lazy" /></a><figcaption>Capturing a screenshot of a single window</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">,</span> SetLastError <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> GetWindowRect<span class="symbol">(</span>IntPtr hwnd<span class="symbol">,</span> <span class="keyword">out</span> RECT lpRect<span class="symbol">)</span><span class="symbol">;</span>

<span class="symbol">[</span>StructLayout<span class="symbol">(</span>LayoutKind<span class="symbol">.</span>Sequential<span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">public</span> <span class="keyword">struct</span> RECT
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">int</span> left<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> top<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> right<span class="symbol">;</span>
 <span class="keyword">public</span> <span class="keyword">int</span> bottom<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureWindow<span class="symbol">(</span>IntPtr hWnd<span class="symbol">)</span>
<span class="symbol">{</span>
 RECT region<span class="symbol">;</span>

 GetWindowRect<span class="symbol">(</span>hWnd<span class="symbol">,</span> <span class="keyword">out</span> region<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureRegion<span class="symbol">(</span>Rectangle<span class="symbol">.</span>FromLTRB<span class="symbol">(</span>region<span class="symbol">.</span>Left<span class="symbol">,</span> region<span class="symbol">.</span>Top<span class="symbol">,</span> region<span class="symbol">.</span>Right<span class="symbol">,</span> region<span class="symbol">.</span>Bottom<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureWindow<span class="symbol">(</span>Form form<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureWindow<span class="symbol">(</span>form<span class="symbol">.</span>Handle<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>Depending on the version of Windows you're using, you may find
that you get slightly unexpected results when calling
<code>Form.Bounds</code> or <code>GetWindowRect</code>. As I don't want to digress
to much, I'll follow up why and how to resolve in another post
(the attached sample application includes the complete code
for both articles).</p>
</blockquote>
<h2 id="capturing-the-active-window">Capturing the active window</h2>
<p>As a slight variation on the previous section, you can use the
<code>GetForegroundWindow</code> API call to get the handle of the active
window.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="symbol">[</span>DllImport<span class="symbol">(</span><span class="string">&quot;user32.dll&quot;</span><span class="symbol">)</span><span class="symbol">]</span>
<span class="keyword">static</span> <span class="keyword">extern</span> IntPtr GetForegroundWindow<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">public</span> Bitmap CaptureActiveWindow<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureWindow<span class="symbol">(</span>GetForegroundWindow<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="capturing-a-single-monitor">Capturing a single monitor</h2>
<p>.NET offers the <code>Screen</code> static class which provides access to
all monitors on your system via the <code>AllScreens</code> property. You
can use the <code>FromControl</code> method to find out which monitor a
form is hosted on, and get the region that represents the
monitor - with or without areas covered by the task bar and
other app bars. This means it trivial to capture the contents of
a given monitor.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1b.png" class="gallery" title="Capturing a screenshot of a specific monitor" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1b.png" alt="Capturing a screenshot of a specific monitor" decoding="async" loading="lazy" /></a><figcaption>Capturing a screenshot of a specific monitor</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> Bitmap CaptureMonitor<span class="symbol">(</span>Screen monitor<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureMonitor<span class="symbol">(</span>monitor<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureMonitor<span class="symbol">(</span>Screen monitor<span class="symbol">,</span> <span class="keyword">bool</span> workingAreaOnly<span class="symbol">)</span>
<span class="symbol">{</span>
 Rectangle region<span class="symbol">;</span>

 region <span class="symbol">=</span> workingAreaOnly <span class="symbol">?</span> monitor<span class="symbol">.</span>WorkingArea <span class="symbol">:</span> monitor<span class="symbol">.</span>Bounds<span class="symbol">;</span>

 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureRegion<span class="symbol">(</span>region<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureMonitor<span class="symbol">(</span><span class="keyword">int</span> index<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureMonitor<span class="symbol">(</span>index<span class="symbol">,</span> <span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureMonitor<span class="symbol">(</span><span class="keyword">int</span> index<span class="symbol">,</span> <span class="keyword">bool</span> workingAreaOnly<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureMonitor<span class="symbol">(</span>Screen<span class="symbol">.</span>AllScreens<span class="symbol">[</span>index<span class="symbol">]</span><span class="symbol">,</span> workingAreaOnly<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="capturing-the-entire-desktop">Capturing the entire desktop</h2>
<p>It is also quite simple to capture the entire desktop without
having to know all the details of monitor arrangements. We just
need to enumerate the available monitors and use
<code>Rectangle.Union</code> to merge two rectangles together. When this is
complete, you'll have one rectangle which describes all
available monitors.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1d.png" class="gallery" title="Capturing a screenshot of the entire desktop" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1d.png" alt="Capturing a screenshot of the entire desktop" decoding="async" loading="lazy" /></a><figcaption>Capturing a screenshot of the entire desktop</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> Bitmap CaptureDesktop<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureDesktop<span class="symbol">(</span><span class="keyword">false</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> Bitmap CaptureDesktop<span class="symbol">(</span><span class="keyword">bool</span> workingAreaOnly<span class="symbol">)</span>
<span class="symbol">{</span>
 Rectangle desktop<span class="symbol">;</span>
 Screen<span class="symbol">[</span><span class="symbol">]</span> screens<span class="symbol">;</span>

 desktop <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Empty<span class="symbol">;</span>
 screens <span class="symbol">=</span> Screen<span class="symbol">.</span>AllScreens<span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> screens<span class="symbol">.</span>Length<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Screen screen<span class="symbol">;</span>

 screen <span class="symbol">=</span> screens<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">;</span>

 desktop <span class="symbol">=</span> Rectangle<span class="symbol">.</span>Union<span class="symbol">(</span>desktop<span class="symbol">,</span> workingAreaOnly <span class="symbol">?</span> screen<span class="symbol">.</span>WorkingArea <span class="symbol">:</span> screen<span class="symbol">.</span>Bounds<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>CaptureRegion<span class="symbol">(</span>desktop<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>There is one slight problem with this approach - if the
resolutions of your monitors are different sizes, or are
misaligned from each other, the gaps will be filled in solid
black. It would be nicer to make these areas transparent,
however at this point in time I don't need to capture the
whole desktop so I'll leave this either as an exercise for the
reader, or a subsequent update.</p>
</blockquote>
<h2 id="capturing-an-arbitrary-region">Capturing an arbitrary region</h2>
<p>Of course, you could just call <code>CaptureRegion</code> with a custom
rectangle to pick up some arbitrary part of the desktop. The
above helpers are just that, helpers!</p>
<h2 id="a-note-on-display-scaling-and-high-dpi-monitors">A note on display scaling and high DPI monitors</h2>
<p>Although I don't have a high DPI monitor, I did temporarily
scale the display to 125% to test that the correct regions were
still captured. I tested with a manifest stating that the
application supported high DPI and again without, in both cases
the correct sized images were captured.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1e.png" class="gallery" title="Capturing a scaled window that supports high DPI" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1e.png" alt="Capturing a scaled window that supports high DPI" decoding="async" loading="lazy" /></a><figcaption>Capturing a scaled window that supports high DPI</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/capture-screenshot-1f.png" class="gallery" title="Capturing a a scaled window that doesn't support high DPI" ><img src="https://images.cyotek.com/image/thumbnail/devblog/capture-screenshot-1f.png" alt="Capturing a a scaled window that doesn't support high DPI" decoding="async" loading="lazy" /></a><figcaption>Capturing a a scaled window that doesn't support high DPI</figcaption></figure><h2 id="the-demo-program">The demo program</h2>
<p>A demonstration program for the techniques in this article is
available from the links below. It's also available on
<a href="https://github.com/cyotek/SimpleScreenshotCapture" rel="external nofollow noopener">GitHub</a>.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2017-08-27 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/capturing-screenshots-using-csharp-and-p-invoke .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com