c# - WPF - Graphics.CopyFromScreen 返回尺寸错误的图像
问题描述
使用 WPF,我想截取一个由矩形定义的区域的屏幕截图,该矩形(x,y)
代表左上角,以及矩形的width
和height
。
在此示例中,source
屏幕截图的矩形由左上角定义,source.x=0 和 source.y=0,source.width=200 和 source.height=200。
为了确保该功能执行我想要的操作,我将屏幕截图作为图像显示在屏幕上。source
我在 position的矩形旁边显示屏幕截图的图像destination
。在本例中,destination
由destination.x = source.x + source.width (=200)、destination.y=0、destination.width 和destination.height = 200 定义。
这是应用程序的最小工作示例:
<Window x:Class="ScreenViewerWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenViewerWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
WindowState="Maximized"
Topmost="True"
AllowsTransparency="True" WindowStyle="None"
>
<Window.Background>
<SolidColorBrush Opacity="0.5" Color="White"/>
</Window.Background>
<Grid Name="grid">
</Grid>
</Window>
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Image = System.Windows.Controls.Image;
namespace ScreenViewerWPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var source = new
{
top = 0,
left = 0,
width = 200,
height = 200
};
var dest = new
{
top = 0,
left = 200,
width = 200,
height = 200
};
//rectangle to visualize the source
grid.Children.Add(new System.Windows.Shapes.Rectangle()
{
Margin = new Thickness(source.left, source.top, 0, 0),
Width = source.width,
Height = source.height,
Stroke = new SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 0, 0)),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
});
// rectangle to visualize the destination
grid.Children.Add(new System.Windows.Shapes.Rectangle()
{
Margin = new Thickness(dest.left, dest.top, 0, 0),
Width = dest.width,
Height = dest.height,
Stroke = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0, 0, 255)),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
});
Bitmap bmp = new Bitmap(120*source.width/96, 120 * source.height/96);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(source.left, source.top, 0, 0, bmp.Size);
bmp.Save("test.jpg", ImageFormat.Jpeg);
// grabed image
Image image = new()
{
Margin = new Thickness(dest.left, dest.top, 0, 0),
Width = dest.width,
Height = dest.height,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
}; grid.Children.Add(image);
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromWidthAndHeight(source.width, source.height)
);
image.Source = bitmapSource;
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
MakeClicThrough();
}
#region make the window clic-through able
// this is for being able to clic through the form
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
const int GWL_EXSTYLE = -20;
const int WS_EX_LAYERED = 0x80000;
const int WS_EX_TRANSPARENT = 0x20;
private void MakeClicThrough()
{
var hwnd = new WindowInteropHelper(this).Handle;
var style = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_LAYERED | WS_EX_TRANSPARENT);
}
#endregion
}
}
请注意这一行Bitmap bmp = new Bitmap(120*source.width/96, 120 * source.height/96);
,这是我用来“尝试”以使事情正常进行的修复。120 是我的计算机 DPI,96 是常数 DPI。
如果没有120/96
,我会得到这个(运行应用程序时屏幕左上角的屏幕截图):
可以看出,两张图片不匹配,右边一张像是“放大了”。
有了 ratio 120/96
,我得到了这个,它更好但还不够好(如您所见,第 41 行在右图的图像中,而在左边它位于矩形下方。
我想在矩形中有“完全”的好图像(或尽可能接近)。如果可能的话,一个优雅的解决方案。
解决方案
在计算屏幕上的实际(物理)几何图形时,您需要考虑 Per-Monitor DPI 的缩放。
(...)
var dpi = VisualTreeHelper.GetDpi(grid);
Bitmap bmp = new((int)(source.width * dpi.DpiScaleX), (int)(source.height * dpi.DpiScaleY));
(...)
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions() // or BitmapSizeOptions.FromWidthAndHeight(bmp.Width, bmp.Height)
);
image.Source = bitmapSource;
Image 的位置仍然看起来很奇怪,但它是由 WindowState 和 WindowStyle 设置引起的,因此不是这里的主题。
推荐阅读
- apache-kafka - 从 Clickhouse 推送数据到 Kafka
- c++ - 使用动态规划的多个可用包的背包问题
- php - 使用 SELECT 查询将大表导出到客户端计算机
- tensorflow - 在 Keras/Tensorflow 中计算困惑度和内存问题
- yocto - 仅当机器名称包含特定子字符串时,如何在 yocto 中添加 bitbake 层?
- python-3.x - 沿轴迭代 numpy.ndarray
- robotframework - 如何从另一个文件调用 xpath?
- javascript - 如何从 MySQL 中的多个表中进行选择?
- php - Laravel:检查验证器失败的原因
- excel - 复制单元格值时如何指定单元格?