WPF: Way to take screenshots

13,763

First you'll need to add references for the following namespaces:

using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

Then enumerate your monitors to get the bounding rectangle for all display surfaces and pass that in to the Graphics.CopyFromScreen() method call:

private static BitmapSource CopyScreen()
{
    var left = Screen.AllScreens.Min(screen => screen.Bounds.X);
    var top = Screen.AllScreens.Min(screen => screen.Bounds.Y);
    var right = Screen.AllScreens.Max(screen => screen.Bounds.X + screen.Bounds.Width);
    var bottom = Screen.AllScreens.Max(screen => screen.Bounds.Y + screen.Bounds.Height);
    var width = right - left;
    var height = bottom - top;

    using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
    {
        using (var bmpGraphics = Graphics.FromImage(screenBmp))
        {
            bmpGraphics.CopyFromScreen(left, top, 0, 0, new System.Drawing.Size(width, height));
            return Imaging.CreateBitmapSourceFromHBitmap(
                screenBmp.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }
    }
}

Bear in mind though that monitors often don't fit neatly into a single rectangle, particularly if they have different resolution etc, so you might be better snap-shotting the individual screens. Either way, the solution to your problem is to change the coordinates that you were passing in to the Graphics.CopyFromScreen() method call.

EDIT: see Demetris Leptos's comment below, the code I've posted in this answer should be calling DeleteObject on the bitmap returned by screenBmp.GetHbitmap() so as to avoid a memory leak, as specified in the MSDN documentation.

Share:
13,763

Related videos on Youtube

Tsukasa
Author by

Tsukasa

Updated on September 15, 2022

Comments

  • Tsukasa
    Tsukasa about 1 year

    Looking to modify the following to take a screenshot off all monitors. I have tried adapting it but my images are blank. Writing it to test.png is for testing. The byte[] will be sent to the receiving app.

    public byte[] Take()
    {
        int screenWidth = Convert.ToInt32(SystemParameters.VirtualScreenWidth);
        int screenHeight = Convert.ToInt32(SystemParameters.VirtualScreenHeight);
        int screenLeft = Convert.ToInt32(SystemParameters.VirtualScreenLeft);
        int screenTop = Convert.ToInt32(SystemParameters.VirtualScreenTop);
    
        RenderTargetBitmap renderTarget = new RenderTargetBitmap(screenWidth, screenHeight, 96, 96, PixelFormats.Pbgra32);
        VisualBrush sourceBrush = new VisualBrush();
    
        DrawingVisual drawingVisual = new DrawingVisual();
        DrawingContext drawingContext = drawingVisual.RenderOpen();
    
        using (drawingContext)
        {
            drawingContext.PushTransform(new ScaleTransform(1, 1));
            drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(screenWidth, screenHeight)));
        }
        renderTarget.Render(drawingVisual);
    
        PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
        pngEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
    
        Byte[] _imageArray;
    
        using (MemoryStream outputStream = new MemoryStream())
        {
            pngEncoder.Save(outputStream);
            _imageArray = outputStream.ToArray();
        }
    
        using (FileStream stream = new FileStream(@"c:\test.png", FileMode.Create, FileAccess.ReadWrite))
        {
            using (BinaryWriter writer = new BinaryWriter(stream))
            {
                writer.Write(_imageArray);
            }
        }
    
        return _imageArray;
    }
    
  • Tsukasa
    Tsukasa almost 10 years
    is there a way to grab each screen as separate capture
  • Mark Feldman
    Mark Feldman almost 10 years
    Absolutely, just enumerate each screen and pass the rectangle {Bounds.X, Bounds.Y, Bounds.X+Bounds.Width, Bounds.Y+Bounds.Height} in as the source rectangle. If you look at the first 4 lines of my code above you'll see I was just creating a rectangle that bounded all of them.
  • McoreD
    McoreD almost 8 years
    Isn't there a way without using Windows.Forms? I might as well not use WPF if I have to use Windows.Forms as a reference.
  • Mark Feldman
    Mark Feldman almost 8 years
    @McoreD no, WPF is used for the presentation of graphics, hence the 'P'. It also uses the DirectX API, which is separate to the GDI API that forms the core interface to the Windows rendering subsystem.
  • Demetris Leptos
    Demetris Leptos almost 7 years
    possible memory leak - posting before I forget to and get lost back into the code: // your code using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1000, 1000)) { IntPtr hBitmap = bmp.GetHbitmap(); try { var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap‌​(hBitmap, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOpti‌​ons()); } finally { DeleteObject(hBitmap) } } from: stackoverflow.com/questions/1546091/…
  • Igor Mironenko
    Igor Mironenko over 2 years
    Yeah can't accept an answer that requires a reference to WinForms when the question is asking for WPF
  • Mark Feldman
    Mark Feldman over 2 years
    @PandaWood WPF is a presentation framework used by individual applications, why would you try to use it to capture the entire Windows desktop, which for most versions of Windows is still based on GDI? Just because WPF provides an alternative/better presentation framework doesn't mean it has replaced WinForms and GDI altogether. The only reason to do anything like this would be to then display that background in a WPF window, but irrespective of which exact technique you use you'd still have to essentially capture it with GDI first and then upload it into a WPF/DirectX texture for display.
  • Igor Mironenko
    Igor Mironenko about 2 years
    @MarkFeldman Thanks, I see your point. And I shouldn't be religously trying to avoid including GDI-based (winforms) libraries - especially if that's the only thing that can do the job