Размер изображения при захвате снимка экрана другого приложения изменяется, если приложение не на 0,0

У меня есть приложение, которое правильно захватывает изображение окна приложения, если оно находится в верхнем левом углу основного экрана. Но если это не так, размер изображения неправильный (высота изображения окна становится растянутой, если оно находится напротив правого поля и вниз от верхнего края экрана. Приложение в 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

Эта простая версия демонстрирует поведение, с которым я имею дело. Если «calc» находится в верхнем левом углу, это прекрасно - переместите его вниз или влево, и изображение будет включать другие части экрана и может обрезать изображение «calc».

Пожалуйста, предоставьте Минимальный, полный и проверяемый пример.

41686d6564 10.08.2018 17:45

Объявление GetWindowRect неверно, getwindowrect (user32). Также прямоугольник называется byref, а не Byval

γηράσκω δ' αεί πολλά διδασκόμε 10.08.2018 19:14
0
2
185
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш код можно упростить в некоторых деталях. Прежде всего, как уже упоминалось в комментариях, ваше объявление GetWindowRect () неверно. Вам необходимо передать ему дескриптор окна, обычно в форме структуры IntPtr и структуры RECT.

Обратитесь к Сайт PInvoke, когда вам нужно включить вызов функции Windows API в свой код. Опыт многих программистов подделал :) эти строчки кода.

Размер рабочего стола здесь возвращается SystemInformation.PrimaryMonitorSize.
Вы также можете использовать Screen.PrimaryScreen.Bounds или SystemInformation.VirtualScreen.
Выберите тот, который лучше всего соответствует вашим планам.

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

Редактировать:
Метод сохранения исходного изображения на диск, уменьшения размера исходного изображения до определенного размера или части его, а затем установки измененного изображения на ClipBoard, готового для вставки в какой-либо получатель.

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

Пример:
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

Большое спасибо так. Ваш пример многому меня научил (и, изучая его, я узнаю больше).

RC Ellis 10.08.2018 22:42

Связанный вопрос: я вижу, что если вы вставите такой снимок экрана в Word, вы можете изменить размер изображения (используя боковые / угловые ручки), и, похоже, он отлично справляется с сохранением разрешения. Есть ли способ сделать то же самое с vb.net? Просто любопытно - но преимущества вижу ...

RC Ellis 13.08.2018 16:40

@RC Ellis Это действительно зависит от того, чего вы пытаетесь достичь. В пользовательском интерфейсе вы можете назначить образ для PictureBox. Его режим Zoom изменит размер изображения, как это делает Word (точно так же), сохраняя качество. Если вы имеете в виду уменьшение размера Bitmap, созданного CopyFromScreen, есть другие соображения. Вы можете сохранить исходный выстрелил и создать из него растровые изображения меньшего размера. Если ваш исходный Bitmap меньше реального размера ... ну, это как фотоаппарат. Когда «разрешение» низкое, качество ниже. Вы ничего не можете с этим поделать.

Jimi 13.08.2018 17:24

@RC Ellis Кстати, если вам нужно сохранить на диск эти скриншоты, используйте формат PNG, нетJPG. В первом есть метод сжатия, который намного лучше подходит для таких растровых изображений.

Jimi 13.08.2018 17:28

На самом деле я вставляю их в richtextbox - я могу представить себе сложную процедуру сохранения изображения в файл как PNG, а затем вставку этого файла в richtextbox - есть ли менее механический / сложный способ сделать это?

RC Ellis 15.08.2018 15:49

Вы имеете в виду, что вставка изображений в RichTextBox, а затем уменьшение или иное изменение их размера, перетаскивание изображения горячие точки, дает менее приятный результат, чем выполнение той же операции в Word? Элемент управления RTB использует для этого те же точные функции API, но не вычисляет AntiAliasing на основе разрешения экрана Dpi (без бикубической интерполяции). Если вы имеете в виду, что предпочитаете предварительно рассчитать меньший размер перед вставкой изображения, это можно сделать (включая настройки InterpolationMode и PixelOffset). Сохранять / загружать образ с диска не нужно.

Jimi 15.08.2018 16:22

Да - мне нужно масштабировать изображение (с наименьшими возможными потерями) без вмешательства пользователя - файл rtf будет использован в документе Word. Прямо сейчас я использую функцию, возвращающую bmp из: DrawImage: g.DrawImage (InputImage, New Rectangle (0, 0, bmp.Width, bmp.Height), New Rectangle (0, 0, InputImage.Width, InputImage.Height) ), GraphicsUnit.Pixel)

RC Ellis 15.08.2018 17:11

См. Правка: предыдущий код был изменен для поддержки изменения размера исходного изображения. Это перегруженный метод, который принимает как фиксированный размер, так и процентное выражение.

Jimi 15.08.2018 20:30

Прохладный. Мне нравится подход метода перегрузки. Гибкий. Спасибо.

RC Ellis 15.08.2018 23:09

Связанный вопрос: где мне искать информацию о конкретном экземпляре окна - например, я запускаю Outlook с отдельным окном для календаря - оба используют один и тот же Pid - как получить прямоугольник для конкретного экземпляра?

RC Ellis 31.08.2018 16:41

@RC Ellis Этот вопрос заслуживает полного вопроса SO. Да, они используют один и тот же основной ProcessID и основной ThreadID, но не тот же дескриптор окна или имя класса. Итак, вы можете использовать EnumChildWindows, чтобы получить дочерние дескрипторы, и GetClassName, чтобы идентифицировать правильный класс. Имя класса малого календаря - rctrl_renwnd32, большого календаря может быть (просмотр недели) DayViewWnd, (просмотр месяца) WeekViewWnd и т. д.

Jimi 31.08.2018 17:57

Спасибо, Джими. Я опубликовал новый вопрос, но похоже, что в вашем ответе неплохо было бы сослаться на «там». Я предполагаю, что перекрестная публикация / копирование - не лучшая идея - есть ли рекомендуемый способ добавить туда ссылку на этот ответ?

RC Ellis 31.08.2018 18:03

@RC Ellis У меня нет на это ответа :) В любом случае, если на ваш новый вопрос пока не будет подходящего ответа, я постараюсь зайти (не могу ничего обещать, я вполне занятый). Если вам нужно скопировать то, что я написал, вы знаете политику здесь. Это все публично. Если вам нужно добавить ссылку, просто скопируйте адрес (URL), указанный в ссылке доля, которую вы найдете под ответом или вопросом.

Jimi 31.08.2018 18:49

Другие вопросы по теме