2

I am trying to save a screenshot of the second monitor which has a different resolution/DPI than the primary monitor (I am not totally familiar with these terminologies so I am not very sure), this is the code that I am using:

public void SaveScreenshot(Screen screen)
{
    using (Bitmap bitmap = new Bitmap(screen.Bounds.Width, screen.Bounds.Height))
    {
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            int x = screen.Bounds.Left;
            int y = screen.Bounds.Top;
            int w = screen.Bounds.Width;
            int h = screen.Bounds.Height;
            g.CopyFromScreen(x, y, 0, 0, new Size(w, h));
        }

        bitmap.Save("screenshot.bmp");
    }
}

The problem is that the resulting image doesn't have the whole picture, instead, it has a larger and cropped one.

Actual: Actual

result: result

I don't know what I'm doing wrong.

Mohammad Ahmad
  • 192
  • 2
  • 12
  • 1
    Read the notes [here](https://stackoverflow.com/a/53026765/7444103), see the section related to the VirtualScreen, stop by the DpiAwaness part, make your app DpiAware and retry. – Jimi Feb 16 '20 at 15:20

1 Answers1

1

I managed to solve it by using EnumDisplaySettings:

using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ScreenResolution
{
    class ScreenResolutionHandler
    {
        const int ENUM_CURRENT_SETTINGS = -1;
        public static Rectangle GetRealRectangle(Screen screen)
        {
            DEVMODE dm = new DEVMODE();
            dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
            EnumDisplaySettings(screen.DeviceName, ENUM_CURRENT_SETTINGS, ref dm);
            return new Rectangle(dm.dmPositionX, dm.dmPositionY, dm.dmPelsWidth, dm.dmPelsHeight);
        }

        [DllImport("user32.dll")]
        public static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);

        [StructLayout(LayoutKind.Sequential)]
        public struct DEVMODE
        {
            private const int CCHDEVICENAME = 0x20;
            private const int CCHFORMNAME = 0x20;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
            public string dmDeviceName;
            public short dmSpecVersion;
            public short dmDriverVersion;
            public short dmSize;
            public short dmDriverExtra;
            public int dmFields;
            public int dmPositionX;
            public int dmPositionY;
            public ScreenOrientation dmDisplayOrientation;
            public int dmDisplayFixedOutput;
            public short dmColor;
            public short dmDuplex;
            public short dmYResolution;
            public short dmTTOption;
            public short dmCollate;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
            public string dmFormName;
            public short dmLogPixels;
            public int dmBitsPerPel;
            public int dmPelsWidth;
            public int dmPelsHeight;
            public int dmDisplayFlags;
            public int dmDisplayFrequency;
            public int dmICMMethod;
            public int dmICMIntent;
            public int dmMediaType;
            public int dmDitherType;
            public int dmReserved1;
            public int dmReserved2;
            public int dmPanningWidth;
            public int dmPanningHeight;
        }
    }
}

and editing the original code to

public void SaveScreenshot(Screen screen)
{
    Rectangle rect = ScreenResolutionHandler.GetRealRectangle(screen);
    int x = rect.X;
    int y = rect.Y;
    int w = rect.Width;
    int h = rect.Height;

    using (Bitmap bitmap = new Bitmap(w, h))
    {
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.CopyFromScreen(x,y, 0, 0, new Size(w, h));
        }

        bitmap.Save("screenshot.bmp");
    }
}

Update:

As @Jimi suggested, eventually I had to enable per-monitor dpi-awareness to maintain the quality of the image when putting it on the Form

app.manifest

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
        <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->

        <!-- Windows Vista -->
        <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->

        <!-- Windows 7 -->
        <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />

        <!-- Windows 8 -->
        <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />

        <!-- Windows 8.1 -->
        <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />

        <!-- Windows 10 -->
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

    </application>
</compatibility>

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- legacy -->
        <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to pm if pmv2 is not available -->
    </windowsSettings>
</application>
Mohammad Ahmad
  • 192
  • 2
  • 12
  • 1
    I suggest you make your App DpiAware instead of PInvoking. You'll need it anyway. Suppose you want to capture a specific Window, instead of the whole screen; you cannot call `GetWindowRect()`, for example, this function (as many others) is not DpiAware: it won't give you the actual size, so you're back to square one. Btw, why are calling `GetRealRectangle()` four times? You just need to call it once. DpiAwareness also affects the way Bitmaps are manipulated and presented on a UI after. There's a really good chance that these Bitmaps become quite blurry (or get clipped) when shown. – Jimi Feb 16 '20 at 17:20
  • I removed the unnecessary calls, thank you. Actually, yes, I am drawing this bitmap on a Form after filling it. Before applying this solution I tried to enable the dpi awareness in app.manifest but the image was still cropped. – Mohammad Ahmad Feb 16 '20 at 17:45
  • 1
    You probably didn't apply [High DPI support in Windows Forms](https://learn.microsoft.com/en-us/dotnet/framework/winforms/high-dpi-support-in-windows-forms) completely. You need to declare support for Window 10 (and Windows 7 / Windows 8.1, eventually) explicitly in the `app.amanifest` (uncommenting the lines you find there). Also un-comment `true`; then add/uncomment `` in `app.config` (as shown in the Docs). – Jimi Feb 16 '20 at 18:12
  • 1
    Note that the `dpiAwareness` (not `dpiAware`) setting in `app.manifest` overrides the corresponding `dpiAwareness` setting in `app.config`. – Jimi Feb 16 '20 at 18:16