0

I have an app which properly captures the image of an application window if it is in the upper-left corner of the primary screen.
But if it is not, the image size is not correct (the window image height becomes stretched if it is against the right margin and down from the screen top. Application at 0,0

Application against right margin of primary screen

Imports System.Data.SqlClient
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic.Strings
Imports System
Imports System.Data
Imports System.Data.OleDb

Public Class Form1
    Public Declare Function GetWindowRect Lib "user32" (ByVal HWND As Integer, ByRef lpRect As Rectangle) As Integer
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub
    Private Sub BtnCapture_Click(sender As Object, e As EventArgs) Handles BtnCapture.Click

        Dim FoundApplication As Boolean = False
        Dim localAll As Process() = Process.GetProcesses()
        Dim rect As New Rectangle
        Dim Top As Int32 = 0
        Dim Left As Int32 = 0
        Dim width As Int32
        Dim height As Int32
        Dim hwnd As IntPtr
        Dim memoryImage As Bitmap

        For Each x As Process In localAll
            GetWindowRect(x.MainWindowHandle, rect)
            If x.ProcessName.ToString = "calc" Then

                width = rect.Width
                height = rect.Height
                Top = rect.Top
                Left = rect.Left
                hwnd = x.MainWindowHandle
                FoundApplication = True
                Exit For

            End If
        Next

        If FoundApplication Then
            ' do nothing - set above
        Else
            ' set the default to entire Primary screen if Calc not found
            width = Screen.PrimaryScreen.Bounds.Width
            height = Screen.PrimaryScreen.Bounds.Height
        End If

        Dim MyGraphics As Graphics = Graphics.FromHwnd(hwnd)
        Dim s As New Size(width, height)
        memoryImage = New Bitmap(width, height, myGraphics)
        Dim memoryGraphics As Graphics = Graphics.FromImage(memoryImage)
        memoryGraphics.CopyFromScreen(Top, Left, 0, 0, s)
        Clipboard.SetImage(memoryImage)

        RtbLog.AppendText(Today().ToShortDateString & " " & Now().ToShortTimeString & vbCrLf)
        RtbLog.Paste()
        myGraphics.Dispose()
    End Sub
End Class

This simple version exhibits the behavior I am dealing with.
If the "calc" is in the upper left corner it's perfect - move it down or to the left and the image includes other parts of the screen and may cut off the image of "calc".

Jimi
  • 29,621
  • 8
  • 43
  • 61
RC Ellis
  • 3
  • 4

1 Answers1

0

Your code can be simplified in some details.
First of all, as already mentioned in the comments, your declaration of GetWindowRect() is not correct. You need to pass it a Window handle, usually in the form of an IntPtr structure, and a RECT structure.

Refer to the PInvoke website when you need to include a Windows API function call in your code. The experience of many programmers has forged :) those lines of code.

The Desktop size, here, is returned by SystemInformation.PrimaryMonitorSize.
You could also use Screen.PrimaryScreen.Bounds or SystemInformation.VirtualScreen.
Choose the one that best fits your plans.

Imports System.Diagnostics
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Runtime.InteropServices

<DllImport("user32.dll")>
Private Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
End Function

<StructLayout(LayoutKind.Sequential)>
Public Structure RECT
    Public Left As Integer
    Public Top As Integer
    Public Right As Integer
    Public Bottom As Integer
End Structure

Private Sub BtnCapture_Click(sender As Object, e As EventArgs) Handles BtnCapture.Click
    Dim wRect As RECT = Nothing
    Dim WindowArea As Rectangle = Nothing

    Dim FindProcess As Process = Process.GetProcessesByName("calc").FirstOrDefault()
    If FindProcess IsNot Nothing AndAlso CInt(FindProcess.MainWindowHandle) > 0 Then
        If GetWindowRect(FindProcess.MainWindowHandle, wRect) Then
            WindowArea = Rectangle.FromLTRB(wRect.Left, wRect.Top, wRect.Right, wRect.Bottom)
        End If
    End If
    If WindowArea = Nothing Then WindowArea = New Rectangle(Point.Empty, SystemInformation.PrimaryMonitorSize)
    Using img As Image = New Bitmap(WindowArea.Width, WindowArea.Height, PixelFormat.Format32bppArgb)
        Using g As Graphics = Graphics.FromImage(img)
            g.SmoothingMode = SmoothingMode.HighQuality
            g.CopyFromScreen(WindowArea.Location, Point.Empty, WindowArea.Size, CopyPixelOperation.SourceCopy)
            img.Save("[The Image Path]", ImageFormat.Png)
            ScaleToClipboard(img, 65.0F) '65% of its original size or 
        End Using
    End Using
    '(...) Other processing
End Sub

Edit:
A method to save the original image to disk, reduce the source Image size to a specific size or to a fraction of it, then set the modified Image to the ClipBoard, ready to be pasted in some recepient.

ScaleToClipboard([Source Image], [Percent of Original] As Single)
ScaleToClipboard([Source Image], [Specific Size] As Size)

Example:
ScaleToClipboard([Source Image], 72.0F)
ScaleToClipboard([Source Image], New Size(200, 125))

Private Sub ScaleToClipboard(SourceImage As Image, SizeScale As Single)
    Dim NewSize As SizeF = New SizeF((SourceImage.Width \ 100) * SizeScale, (SourceImage.Height \ 100) * SizeScale)
    ScaleToClipboard(SourceImage, Size.Round(NewSize))
End Sub

Private Sub ScaleToClipboard(SourceImage As Image, SizeScale As Size)
    Using img As Image = New Bitmap(SourceImage, Size.Round(SizeScale))
        Using g As Graphics = Graphics.FromImage(img)
            g.SmoothingMode = SmoothingMode.HighQuality
            g.InterpolationMode = InterpolationMode.HighQualityBicubic
            g.DrawImage(SourceImage, New Rectangle(Point.Empty, SizeScale))
            Clipboard.SetImage(TryCast(img.Clone(), Image))
        End Using
    End Using
End Sub
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thank you **so** much. Your example taught me a great deal (and as I study it I'm learning more). – RC Ellis Aug 10 '18 at 20:42
  • A related question: I see that if you paste such a screenshot into Word you can resize the image (using the side/corner handles) and it seems to do an excellent job of preserving resolution. Is there a way to accomplish the same from vb.net? Just curious - but I can see advantages... – RC Ellis Aug 13 '18 at 14:40
  • @RC Ellis This really depends on what you're trying to accomplish. In the UI, you can assign the Image to a `PictureBox`. Its `Zoom` mode will resize the Image as Word does (exactly the same), preserving the quality. If you mean reducing the size of the Bitmap generated by `CopyFromScreen`, there are different considerations. You can keep the original *shot* and create smaller Bitmaps from it. If your initial Bitmap is smaller then the real size... well, it's like a photocamera. When the "resolution" is low, the quality is lower. Nothing you can do about it. – Jimi Aug 13 '18 at 15:24
  • @RC Ellis BTW, if you need to save to disk those screenshots, use the `PNG` format, **not** `JPG`. The former has a compression method that's much better suited for these kind of Bitmaps. – Jimi Aug 13 '18 at 15:28
  • Actually I'm pasting them into a richtextbox - I can envision an involved routine of saving the image to a file as PNG, then pasting that file into the richtextbox - is there a less mechanical/involved way to do it? – RC Ellis Aug 15 '18 at 13:49
  • Do you mean that pasting images in a `RichTextBox` and then reducing or otherwise adapting their size, dragging the image *hotspots*, has a less pleasing result than performing the same operation in Word? The RTB control uses the same exact API functions for this, but it does not calculate the AntiAliasing based on the screen Dpi resolution (no Bicubic Interpolation). If you mean that you'ld prefer to pre-calculate a smaller size before pasting the image, it can be done (including `InterpolationMode` an `PixelOffset` adjustments). Saving/Loading the image from disc is not necessary. – Jimi Aug 15 '18 at 14:22
  • Yes - I need to scale the image (with least loss possible) without user intervention - the rtf file will be consumed in a Word document. Right now I am using a function returning a bmp from: DrawImage: g.DrawImage(InputImage, New Rectangle(0, 0, bmp.Width, bmp.Height), New Rectangle(0, 0, InputImage.Width, InputImage.Height), GraphicsUnit.Pixel) – RC Ellis Aug 15 '18 at 15:11
  • See the Edit: the previous code has been modified to support the resizing of the original Image. It's an overloaded method that accepts both a fixed size and a percentual expression. – Jimi Aug 15 '18 at 18:30
  • Cool. I like the overload method approach. Flexible. Thanks. – RC Ellis Aug 15 '18 at 21:09
  • A related question: where would I look on getting information on a specific instance of a window - for example, I run Outlook with a separate window for calendar - both share the same Pid - how to I get the rectangle for a specific instance? – RC Ellis Aug 31 '18 at 14:41
  • @RC Ellis This question deserves a full SO question. Yes, they share the same main `ProcessID` and main `ThreadID`, but not the same Window Handle or Class Name. So, you can use [EnumChildWindows](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumchildwindows) to get the children Handles and [GetClassName](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getclassname) to identify the right class. The small calendar class name is `rctrl_renwnd32`, the large calendar can be (week view) `DayViewWnd`, (month view) `WeekViewWnd` etc. – Jimi Aug 31 '18 at 15:57
  • Thanks, Jimi. I did post a fresh question but it looks like your answer would be good to reference 'over there'. I presume cross-posting/copying over isn't a good idea - is there a recommended way to add a reference to this answer there? – RC Ellis Aug 31 '18 at 16:03
  • @RC Ellis I don't have an answer to that :) Anyway, if your new question doesn't receive a suitable answer in the meantime, I'll try to come by (can't make any promise, I'm quite busy). If you need to copy over something I wrote, you know the policy here. It's all public. If you need to add a reference, just copy the address (URL) provided by the **share** link you find below the answer or the question. – Jimi Aug 31 '18 at 16:49