locked
Capture screen with high DPI RRS feed

  • Question

  • I have the following code:

            Dim rect As New Rectangle(Point.Empty, Screen.PrimaryScreen.Bounds.Size)                     'Creating a rectangle of Screen's Size
            Dim bmp As New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height) 'Creating a Bitmap of Screen's Size
            Dim g As Graphics = Graphics.FromImage(bmp)                                                  'Creating Graphics from the Bitmap
            g.CopyFromScreen(Point.Empty, Point.Empty, Screen.PrimaryScreen.Bounds.Size)                 'Copying the Screen
    
            PictureBox1.Image = bmp         'Using the bitmap in our PictureBox
            g.Dispose()             'Disposing our Graphics
    
            PictureBox1.Image.Save(My.Computer.FileSystem.SpecialDirectories.Desktop & "\shot5.png", Imaging.ImageFormat.Png)

    When the picture is saved the picture saves correctly.

    But with machines that have the DPI set higher then normal, part of the screen is not shown in the saved image. What can I do to get this to show the whole window.

    Thanks


    • Edited by Joe Medford Monday, January 4, 2016 1:29 AM change now to not
    Sunday, January 3, 2016 11:44 PM

Answers

  •  After seeing LeonCS`s post this morning i realized i actually used the wrong 2 DeviceCaps values in my 2nd example.  I know i read that the DESKTOPVERTRES and DESKTOPHORZRES where not effected by the screen scaling the same way that the Screen.PrimaryScreen.Bounds property is but,  i guess i was to beat last night to realize my mistake of using the HORZRES and VERTRES.

     I ran a quick test just to see what these values and the Primary.Screen.Bounds.Size would report on the 3 settings  100%,  125%,  and 150%.  My Desktop resolution was set at 1680x1050 and here is what they showed.

    @ 100 %
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 125%
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 150%
    Primary.Screen.Bounds: {Width=1120, Height=700}
    HORZRES: 1120
    VERTRES: 700
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680

    So, from this quick simple test the only one that appears you should need is the DESKTOPVERTRES and DESKTOPHORZRES which appears to report the correct screen size on all 3 settings.  However,  you may want to do some more testing,  i really don`t feel like continuously logging in and out to test all the different combinations of settings that are possible.  You can try this version which works fine on my end.

    Imports System.Runtime.InteropServices

    Public Class Form1
        Private Const DESKTOPVERTRES As Integer = &H75
        Private Const DESKTOPHORZRES As Integer = &H76

        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As New Size(0, 0)

            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ss.Width = GetDeviceCaps(hDc, DESKTOPHORZRES)
                ss.Height = GetDeviceCaps(hDc, DESKTOPVERTRES)
                g.ReleaseHdc(hDc)
            End Using

            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using

                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
            End Using
        End Sub
    End Class


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, January 4, 2016 2:00 PM
    • Proposed as answer by tommytwotrain Monday, January 4, 2016 4:25 PM
    • Marked as answer by Joe Medford Monday, January 4, 2016 5:35 PM
    Monday, January 4, 2016 11:27 AM

All replies

  • I am not positive at this second but,  you might be able to do it something like this to set your application to be DPI aware.  It worked for someone else i helped a while back but,  i have not tried it on my Win7 machine yet.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        <DllImport("user32.dll", EntryPoint:="SetProcessDPIAware")> Private Shared Function SetProcessDPIAware() As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Dim success As Boolean = SetProcessDPIAware()
            If Not success Then
                Me.Text = "SetProcessDPIAware Failed"
            End If
        End Sub
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As Size = Screen.PrimaryScreen.Bounds.Size
    
            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using
    
                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
    
            End Using
        End Sub
    End Class
    


    If you say it can`t be done then i`ll try it

    Monday, January 4, 2016 1:05 AM
  •  You might also be able to use the GetDevCap api to get the actual scaled screen size.  I did not test it but,  from what i just read,  it might work.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Const HORZRES As Integer = &H8
        Private Const VERTRES As Integer = &HA
    
        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As New Size(0, 0)
    
            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ss.Width = GetDeviceCaps(hDc, HORZRES)
                ss.Height = GetDeviceCaps(hDc, VERTRES)
                g.ReleaseHdc(hDc)
            End Using
    
            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using
    
                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
            End Using
        End Sub
    End Class


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, January 4, 2016 1:33 AM
    Monday, January 4, 2016 1:33 AM
  • it take a screenshot correctly, but still fails when the user has a high DPI
    Monday, January 4, 2016 1:34 AM
  •  You might also be able to use the GetDevCap api to get the actual scaled screen size.  I did not test it but,  from what i just read,  it might work.

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Const HORZRES As Integer = &H8
        Private Const VERTRES As Integer = &HA
    
        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As New Size(0, 0)
    
            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ss.Width = GetDeviceCaps(hDc, HORZRES)
                ss.Height = GetDeviceCaps(hDc, VERTRES)
                g.ReleaseHdc(hDc)
            End Using
    
            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using
    
                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
            End Using
        End Sub
    End Class


    If you say it can`t be done then i`ll try it

    GetDeviceCaps  looks like that is for C++, I and trying this in VB.net
    Monday, January 4, 2016 1:37 AM
  • GetDeviceCaps  looks like that is for C++, I and trying this in VB.net

     No, that is VB.Net code that uses the GetDeviceCaps Win32 Api function.  I just wrote it.

    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, January 4, 2016 1:45 AM
    Monday, January 4, 2016 1:44 AM
  • Got the code to work.   It still cuts the window

    Monday, January 4, 2016 2:20 AM
  • Got the code to work.   It still cuts the window

    This is such a pain.

    I think you need to use the monitor dpi (that is the newest way, there is also system and normal). I have not tried it.

    Maybe you can try it Razerz if you have an example running?  Multiply the value from PrimaryScreen by 96/ monitor dpi or similar using api.

    There are lots of variations see Writing DPI Aware Apps.

    We got into this in Joe's other question if you look towards the bottom. I am not really sure about it.

    Monday, January 4, 2016 2:47 AM
  • Will look into this. I have been delaying this part of the program, as long a I can. DPI is something very new to me..
    Monday, January 4, 2016 3:19 AM
  • Will look into this. I have been delaying this part of the program, as long a I can. DPI is something very new to me..

    The PhysicalToLogicalPointForPerMonitorDPI function seems to be a function that one might use to convert from primary to logical size?
    Monday, January 4, 2016 3:24 AM
  • Got the code to work.   It still cuts the window

    This is such a pain.

    I think you need to use the monitor dpi (that is the newest way, there is also system and normal). I have not tried it.

    Maybe you can try it Razerz if you have an example running?  Multiply the value from PrimaryScreen by 96/ monitor dpi or similar using api.

    There are lots of variations see Writing DPI Aware Apps.

    We got into this in Joe's other question if you look towards the bottom. I am not really sure about it.

     I have not tried the GetDpiForMonitor api yet because,  the msdn documents say the minimum supported client for it is Win 8.1 [desktop apps only] and i only have Win7 on a laptop.  Something seems screwy though because,  i believe i remember Monkeyboy trying something with that on Win7 and i couldn`t try it because i only had XP at the time.  Now it says Win8.1 is the minimum.  I noticed the same thing with another api for this topic tonight too,  it was changed from Win7 to Win8 as the minimum.

        Yes,  this is a pain in the butt!!!  I wish MS would fix this stuff but,  i would not hold my breath on that to long.  I am getting off here for the night but,  maybe i will give it a shot tomorrow.   8)


    If you say it can`t be done then i`ll try it

    Monday, January 4, 2016 3:25 AM
  • Device cap number 117 and 118 provide the factory pixel dimension. If you compare these with the virtual pixel dimension you can get a handle on the magnitude of the scaling, and therefore inversely adjust your copyfromscreen dimensions to capture the wanted area.

    Imports System.Runtime.InteropServices
    Public Class Form12
        <DllImport("gdi32.dll")> _
        Private Shared Function GetDeviceCaps(hdc As IntPtr, nIndex As Integer) As Integer
        End Function
    
        Private Sub Form12_Load(sender As Object, e As System.EventArgs) Handles Me.Load
            Dim g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
            Dim desktop As IntPtr = g.GetHdc()
    
            MessageBox.Show(GetDeviceCaps(desktop, 4).ToString & "  Physical width mm" & vbCrLf & _
                            GetDeviceCaps(desktop, 6).ToString & "  Physical height mm" & vbCrLf & _
                            GetDeviceCaps(desktop, 8).ToString & "  Virtual pixel width" & vbCrLf & _
                            GetDeviceCaps(desktop, 10).ToString & "  Virtual pixel height" & vbCrLf & _
                            GetDeviceCaps(desktop, 118).ToString & "  Factory pixel width" & vbCrLf & _
                            GetDeviceCaps(desktop, 117).ToString & "  Factory pixel height")
    
            'MessageBox.Show(GetDeviceCaps(desktop, 116).ToString & "  Video Hardware refresh rate Hertz. (FPS).")
    
        End Sub
    
        Public Enum DeviceCap
            ''' <summary>
            ''' Device driver version
            ''' </summary>
            DRIVERVERSION = 0
            ''' <summary>
            ''' Device classification
            ''' </summary>
            TECHNOLOGY = 2
            ''' <summary>
            ''' Horizontal size in millimeters
            ''' </summary>
            HORZSIZE = 4
            ''' <summary>
            ''' Vertical size in millimeters
            ''' </summary>
            VERTSIZE = 6
            ''' <summary>
            ''' Horizontal width in pixels
            ''' </summary>
            HORZRES = 8
            ''' <summary>
            ''' Vertical height in pixels
            ''' </summary>
            VERTRES = 10
            ''' <summary>
            ''' Number of bits per pixel
            ''' </summary>
            BITSPIXEL = 12
            ''' <summary>
            ''' Number of planes
            ''' </summary>
            PLANES = 14
            ''' <summary>
            ''' Number of brushes the device has
            ''' </summary>
            NUMBRUSHES = 16
            ''' <summary>
            ''' Number of pens the device has
            ''' </summary>
            NUMPENS = 18
            ''' <summary>
            ''' Number of markers the device has
            ''' </summary>
            NUMMARKERS = 20
            ''' <summary>
            ''' Number of fonts the device has
            ''' </summary>
            NUMFONTS = 22
            ''' <summary>
            ''' Number of colors the device supports
            ''' </summary>
            NUMCOLORS = 24
            ''' <summary>
            ''' Size required for device descriptor
            ''' </summary>
            PDEVICESIZE = 26
            ''' <summary>
            ''' Curve capabilities
            ''' </summary>
            CURVECAPS = 28
            ''' <summary>
            ''' Line capabilities
            ''' </summary>
            LINECAPS = 30
            ''' <summary>
            ''' Polygonal capabilities
            ''' </summary>
            POLYGONALCAPS = 32
            ''' <summary>
            ''' Text capabilities
            ''' </summary>
            TEXTCAPS = 34
            ''' <summary>
            ''' Clipping capabilities
            ''' </summary>
            CLIPCAPS = 36
            ''' <summary>
            ''' Bitblt capabilities
            ''' </summary>
            RASTERCAPS = 38
            ''' <summary>
            ''' Length of the X leg
            ''' </summary>
            ASPECTX = 40
            ''' <summary>
            ''' Length of the Y leg
            ''' </summary>
            ASPECTY = 42
            ''' <summary>
            ''' Length of the hypotenuse
            ''' </summary>
            ASPECTXY = 44
            ''' <summary>
            ''' Shading and Blending caps
            ''' </summary>
            SHADEBLENDCAPS = 45
    
            ''' <summary>
            ''' Logical pixels inch in X
            ''' </summary>
            LOGPIXELSX = 88
            ''' <summary>
            ''' Logical pixels inch in Y
            ''' </summary>
            LOGPIXELSY = 90
    
            ''' <summary>
            ''' Number of entries in physical palette
            ''' </summary>
            SIZEPALETTE = 104
            ''' <summary>
            ''' Number of reserved entries in palette
            ''' </summary>
            NUMRESERVED = 106
            ''' <summary>
            ''' Actual color resolution
            ''' </summary>
            COLORRES = 108
    
            ' Printing related DeviceCaps. These replace the appropriate Escapes
            ''' <summary>
            ''' Physical Width in device units
            ''' </summary>
            PHYSICALWIDTH = 110
            ''' <summary>
            ''' Physical Height in device units
            ''' </summary>
            PHYSICALHEIGHT = 111
            ''' <summary>
            ''' Physical Printable Area x margin
            ''' </summary>
            PHYSICALOFFSETX = 112
            ''' <summary>
            ''' Physical Printable Area y margin
            ''' </summary>
            PHYSICALOFFSETY = 113
            ''' <summary>
            ''' Scaling factor x
            ''' </summary>
            SCALINGFACTORX = 114
            ''' <summary>
            ''' Scaling factor y
            ''' </summary>
            SCALINGFACTORY = 115
    
            ''' <summary>
            ''' Current vertical refresh rate of the display device (for displays only) in Hz
            ''' </summary>
            VREFRESH = 116
            ''' <summary>
            ''' Vertical height of entire desktop in pixels
            ''' </summary>
            DESKTOPVERTRES = 117
            ''' <summary>
            ''' Horizontal width of entire desktop in pixels
            ''' </summary>
            DESKTOPHORZRES = 118
            ''' <summary>
            ''' Preferred blt alignment
            ''' </summary>
            BLTALIGNMENT = 119
        End Enum
    
    End Class


    Top Tip: Toothache? Cut paper towel to 2"square. Smear with olive oil. Sprinkle on Cayenne Pepper. Fold over few times to form small wad. Tuck in between wall of mouth and gum. Leave 1 - 2 hrs. You will thank me!


    • Edited by LeonCS Monday, January 4, 2016 5:06 AM
    Monday, January 4, 2016 4:53 AM
  •  After seeing LeonCS`s post this morning i realized i actually used the wrong 2 DeviceCaps values in my 2nd example.  I know i read that the DESKTOPVERTRES and DESKTOPHORZRES where not effected by the screen scaling the same way that the Screen.PrimaryScreen.Bounds property is but,  i guess i was to beat last night to realize my mistake of using the HORZRES and VERTRES.

     I ran a quick test just to see what these values and the Primary.Screen.Bounds.Size would report on the 3 settings  100%,  125%,  and 150%.  My Desktop resolution was set at 1680x1050 and here is what they showed.

    @ 100 %
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 125%
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 150%
    Primary.Screen.Bounds: {Width=1120, Height=700}
    HORZRES: 1120
    VERTRES: 700
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680

    So, from this quick simple test the only one that appears you should need is the DESKTOPVERTRES and DESKTOPHORZRES which appears to report the correct screen size on all 3 settings.  However,  you may want to do some more testing,  i really don`t feel like continuously logging in and out to test all the different combinations of settings that are possible.  You can try this version which works fine on my end.

    Imports System.Runtime.InteropServices

    Public Class Form1
        Private Const DESKTOPVERTRES As Integer = &H75
        Private Const DESKTOPHORZRES As Integer = &H76

        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As New Size(0, 0)

            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ss.Width = GetDeviceCaps(hDc, DESKTOPHORZRES)
                ss.Height = GetDeviceCaps(hDc, DESKTOPVERTRES)
                g.ReleaseHdc(hDc)
            End Using

            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using

                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
            End Using
        End Sub
    End Class


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Monday, January 4, 2016 2:00 PM
    • Proposed as answer by tommytwotrain Monday, January 4, 2016 4:25 PM
    • Marked as answer by Joe Medford Monday, January 4, 2016 5:35 PM
    Monday, January 4, 2016 11:27 AM
  •  After seeing LeonCS`s post this morning i realized i actually used the wrong 2 DeviceCaps values in my 2nd example.  I know i read that the DESKTOPVERTRES and DESKTOPHORZRES where not effected by the screen scaling the same way that the Screen.PrimaryScreen.Bounds property is but,  i guess i was to beat last night to realize my mistake of using the HORZRES and VERTRES.

     I ran a quick test just to see what these values and the Primary.Screen.Bounds.Size would report on the 3 settings  100%,  125%,  and 150%.  My Desktop resolution was set at 1680x1050 and here is what they showed.

    @ 100 %
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 125%
    Primary.Screen.Bounds: {Width=1680, Height=1050}
    HORZRES: 1680
    VERTRES: 1050
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680


    @ 150%
    Primary.Screen.Bounds: {Width=1120, Height=700}
    HORZRES: 1120
    VERTRES: 700
    DESKTOPVERTRES: 1050
    DESKTOPHORZRES: 1680

    So, from this quick simple test the only one that appears you should need is the DESKTOPVERTRES and DESKTOPHORZRES which appears to report the correct screen size on all 3 settings.  However,  you may want to do some more testing,  i really don`t feel like continuously logging in and out to test all the different combinations of settings that are possible.  You can try this version which works fine on my end.

    Imports System.Runtime.InteropServices

    Public Class Form1
        Private Const DESKTOPVERTRES As Integer = &H75
        Private Const DESKTOPHORZRES As Integer = &H76

        <DllImport("gdi32.dll")> Private Shared Function GetDeviceCaps(ByVal hdc As IntPtr, ByVal nIndex As Integer) As Integer
        End Function

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Dim ss As New Size(0, 0)

            Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
                Dim hDc As IntPtr = g.GetHdc
                ss.Width = GetDeviceCaps(hDc, DESKTOPHORZRES)
                ss.Height = GetDeviceCaps(hDc, DESKTOPVERTRES)
                g.ReleaseHdc(hDc)
            End Using

            Using bm As New Bitmap(ss.Width, ss.Height)
                Using g As Graphics = Graphics.FromImage(bm)
                    g.CopyFromScreen(Point.Empty, Point.Empty, ss, CopyPixelOperation.SourceCopy)
                End Using

                bm.Save("C:\testfolder\ScreenShot.png", Imaging.ImageFormat.Png)
            End Using
        End Sub
    End Class


    If you say it can`t be done then i`ll try it


    Worked Perfectly.    Got past the hard part.   Now I will be integrating this into several of my programs.   [Remote desktop, Screen recorder, and a few others].   I thank everyone so much
    Monday, January 4, 2016 2:56 PM
  • Worked Perfectly.    Got past the hard part.   Now I will be integrating this into several of my programs.   [Remote desktop, Screen recorder, and a few others].   I thank everyone so much

     You`re welcome.  8)

     Please don`t forget to mark the post or posts that answered your question as the answer(s).  Thanks.


    If you say it can`t be done then i`ll try it

    Monday, January 4, 2016 3:32 PM