wpf截屏系列
第一章 使用GDI+实现截屏(本章)
第二章 使用DockPanel制作截屏框
第三章 实现截屏框实时截屏
第四章 使用ffmpeg命令行实现录屏

文章目录
wpf截屏系列
前言
一、引用System.Drawing
方法一、引用系统程序集
方法二、NuGet获取跨平台Drawing
二、实现截屏
1.简单截屏
2.绘制鼠标
3.转换成wpf对象
4.屏幕采集
三、完整代码
四、使用示例
1.截屏
2.屏幕采集
示例一、显示桌面
示例二、动态调整参数
总结
前言
wpf做屏幕录制或者屏幕广播之类的功能时需要实现截屏,在C#中比较容易实现的截屏方法是使用GDI+,本文将展示使用GDI+截屏的具体实现方案,包括如何绘制鼠标,按帧率采集屏幕、将GDI+对象转成wpf对象等。

一、引用System.Drawing
在wpf中使用GDI+功能需要引入System.Drawing库,有2种方式:在.net framework中直接引用系统库即可。在.net core中可以引用mono实现的跨平台的System.Drawing,提供接口与系统程序集是一模一样的,而且性能略好一些。

方法一、引用系统程序集
1、右键引用

2、搜索drawing,勾选后确定即可。

方法二、NuGet获取跨平台Drawing
在.net core中无法引用系统的Drawing,只能通过Nuget获取跨平台Drawing。
1、右键引用打开NuGet界面

2、搜索drawing并安装

二、实现截屏
1.简单截屏
简单的截屏只需几行代码即可实现:

/// <summary>
/// 截取一帧图片
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
/// <param name="width">宽</param>
/// <param name="height">高</param>
/// <returns>截屏后的位图对象,需要调用Dispose手动释放资源。</returns>
public static System.Drawing.Bitmap Snapshot(int x, int y, int width, int height)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), System.Drawing.CopyPixelOperation.SourceCopy);
}
return bitmap;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.绘制鼠标
上述方式实现的截屏是没有鼠标的,如果要显示鼠标则需要我们手动绘制,通过获取鼠标的icon绘制到背景图像中。绘制鼠标需要用到win32Api以及gdi的rop。大致步骤如下(示例):

CURSORINFO ci;
ICONINFO info = new ICONINFO();
ci.cbSize = Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out ci))
{
if (GetIconInfo(ci.hCursor, info))
{
if (异或光标)
{
使用gdi的rop绘制
}
else
{
using (var icon = System.Drawing.Icon.FromHandle(ci.hCursor))
{
graphics.DrawIcon(icon, mouseX, mouseY);
}
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3.转换成wpf对象
参考我的另一篇文章《C# wpf Bitmap转换成WriteableBitmap(BitmapSource)的方法》

4.屏幕采集
基于上面的实现加上开线程及循环截屏就可以做到屏幕采集了。示例代码如下:

System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
{
while (!_exitFlag)
{
graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), System.Drawing.CopyPixelOperation.SourceCopy);
//绘制鼠标
...
//绘制鼠标--end
//将位图数据写入wpf对象、编码推流等
...
//将位图数据写入wpf对象、编码推流等--end
Thread.Sleep(帧率延时);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
三、完整代码
通过上述方法得到的接口设计如下(不含具体实现):

/// <summary>
/// 截屏事件参数
/// </summary>
public class ScreenCaptureEventArgs : EventArgs
{
/// <summary>
/// 像素格式
/// </summary>
public System.Drawing.Imaging.PixelFormat PixelFormat { set; get; }
/// <summary>
/// 图像宽
/// </summary>
public int Width { set; get; }
/// <summary>
/// 图像高
/// </summary>
public int Height { set; get; }
}
/// <summary>
/// 截屏数据事件参数
/// </summary>
public class ScreenCaptureDataEventArgs : ScreenCaptureEventArgs
{
/// <summary>
/// 图像数据
/// </summary>
public IntPtr Data { set; get; }
/// <summary>
/// 数据长度
/// </summary>
public int Length { set; get; }
/// <summary>
/// 一行数据长度
/// </summary>
public int Stride { set; get; }
}
/// <summary>
/// 数值类型
/// </summary>
public enum ScreenCaptureValueType
{
/// <summary>
/// 实际值
/// </summary>
TrueValue,
/// <summary>
/// 按比例计算
/// </summary>
RadioValue
}
/// <summary>
/// 截屏对象
/// </summary>
public class ScreenCapture
{
/// <summary>
/// 截屏事件,每截取一帧都会回调
/// </summary>
public event EventHandler<ScreenCaptureDataEventArgs> Captured;
/// <summary>
/// 截屏开始时回调
/// </summary>
public event EventHandler<ScreenCaptureEventArgs> Started;
/// <summary>
/// 结束时回调
/// </summary>
public event EventHandler Stoped;
/// <summary>
/// 截屏是否已停止
/// </summary>
public bool IsStoped { private set; get; }
/// <summary>
/// 是否截取鼠标
/// </summary>
public bool IsPaintMouse { set; get; } = true;
/// <summary>
/// 截屏区域的计算方式
/// TrueValue为实际值。RatioValue为比例值,范围0-1,全屏设为0,0,1,1,则无论任何设备任何分辨率都是截取全屏。
/// </summary>
public ScreenCaptureValueType ClipRectValueType { private set; get; } = ScreenCaptureValueType.RadioValue;
/// <summary>
/// 截屏区域X坐标
/// </summary>
public double ClipX { private set; get; } = 0;
/// <summary>
/// 截屏区域Y坐标
/// </summary>
public double ClipY { private set; get; } = 0;
/// <summary>
/// 截屏区域宽
/// </summary>
public double ClipWidth { private set; get; } = 1;
/// <summary>
/// 截屏区域高
/// </summary>
public double ClipHeight { private set; get; } = 1;
/// <summary>
/// 截屏帧率
/// </summary>
public double Framerate{ set; get; }=30;
/// <summary>
/// 设置截屏区域
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
/// <param name="width">宽</param>
/// <param name="height">高</param>
/// <param name="valueType">TrueValue为实际值。RatioValue为比例值,范围0-1,全屏设为0,0,1,1,则无论任何设备任何分辨率都是截取全屏。</param>
public void SetClipRect(double x, double y, double width, double height, ScreenCaptureValueType valueType);
/// <summary>
/// 启动屏幕采集
/// </summary>
public void Start();
/// <summary>
/// 停止屏幕采集
/// 异步方法,Stoped事件为真正的停止。
/// </summary>
public void Stop();
/// <summary>
/// 截取一帧图片
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
/// <param name="width">宽</param>
/// <param name="height">高</param>
/// <param name="isPaintMouse">是否绘制鼠标</param>
/// <returns>截屏后的位图对象,需要调用Dispose手动释放资源。</returns>
public static System.Drawing.Bitmap Snapshot(int x, int y, int width, int height, bool isPaintMouse);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
完整代码如下:
https://download.csdn.net/download/u013113678/71984470

四、使用示例
1.截屏
xaml

<Window x:Class="WpfScreenCaptureGdi.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:WpfScreenCaptureGdi"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Cursor="Cross">
<Image x:Name="img" ></Image>
</Grid>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
cs

public MainWindow()
{
InitializeComponent();
var bm = ScreenCapture.Snapshot(0, 0, 1920, 1080, true);
var wb = BitmapInterop.BitmapToWriteableBitmap(bm);
img.Source = wb;
bm.Dispose();
}
1
2
3
4
5
6
7
8
效果预览:

2.屏幕采集
示例一、显示桌面
xaml

<Window x:Class="WpfScreenCaptureGdi.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:WpfScreenCaptureGdi"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Closing="Window_Closing"
>
<Grid Cursor="Cross">
<Image x:Name="img" ></Image>
</Grid>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cs

ScreenCapture sc = new ScreenCapture();
public MainWindow()
{
InitializeComponent();
//注册事件
sc.Captured += Sc_Captured;
sc.Started += Sc_Started;
//开始采集
sc.Start();
}

private void Sc_Started(object sender, ScreenCaptureEventArgs e)
{
Dispatcher.Invoke(() =>
{
//初始化位图对象
img.Source = BitmapInterop.CreateCompatibleWriteableBitmap(e.Width, e.Height, e.PixelFormat);
});
}

private void Sc_Captured(object sender, ScreenCaptureDataEventArgs e)
{
//采集的画面用于显示
Dispatcher.Invoke(() =>
{
var wb = img.Source as WriteableBitmap;
if (wb.Width < e.Width || wb.Height < e.Height)
//宽高改变了重新初始化位图对象
{
wb = BitmapInterop.CreateCompatibleWriteableBitmap(e.Width, e.Height, e.PixelFormat);
img.Source = wb;
}
wb.WritePixels(new Int32Rect(0, 0, e.Width, e.Height), e.Data, e.Length, e.Stride, 0, 0);
});
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//异步的方式退出才不会造成死锁
if (!sc.IsStoped)
{
sc.Stop();
sc.Stoped += (s, e) =>
{
Dispatcher.Invoke(() =>
{
Close();
});
};
e.Cancel = true;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

示例二、动态调整参数
可以在采集过程中动态调整参数,比如采集区域、帧率、鼠标绘制。
在示例一的基础上添加如下代码:

//测试动态调整参数
var t = new Thread(() =>
{
while (true)
{
for (int i = 1; i <= 100; i++)
{
sc.SetClipRect(0, 0, i / 100.0, i / 100.0, ScreenCaptureValueType.RadioValue);
Thread.Sleep(100);
}
for (int i = 1; i <= 1920; i++)
{
sc.SetClipRect(0, 0, i, 1080, ScreenCaptureValueType.TrueValue);
Thread.Sleep(1);
}
}
});
t.IsBackground = true;
t.Start();
//测试动态调整参数 --end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
效果预览:

总结
以上就是今天要讲的内容,本文简单介绍GDI+截屏的方法,添加鼠标的实现以及将GDI+对象转换成wpf对象,和屏幕采集的实现,总的来说不算是特别容易,原理很简单但是有不少细节需要处理,尤其是调试中出现资源释放问题,需要有c++开发的意识,才能很好的定位和解决问题。
————————————————
版权声明:本文为CSDN博主「CodeOfCC」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013113678/article/details/121874479

C# wpf 使用GDI+实现截屏的更多相关文章

  1. 【WPF】WPF截屏

    原文:[WPF]WPF截屏 引言 .NET的截图控件在网上流传得不多啊,难得发现一个精品截图控件( 传送门),但是无奈是winform的.后来又找到一个周银辉做的WPF截图(继续传送门),发现截屏是实 ...

  2. c# wpf 利用截屏键实现截屏功能

    原文:c# wpf 利用截屏键实现截屏功能     最近做一个wpf程序需要截图功能,查找资料费了一些曲折,跟大家分享一下.     先是找到了这样一份代码:     static class Scr ...

  3. MonoGame 3.2 下,截屏与 Texture2D 的保存

    10月20日注:后来发现了这篇博文(英文),XNA 中的 Color 实际上是与 Alpha 值自左乘(premultiplied)的,这也解释了直接用 0xARGB 转译而颜色异常的原因. 注意,由 ...

  4. 老话题:自己编写只截窗口客户区的截屏软件(VB2010)

    现在能实现截屏的软件很多,就不一一列举了,连WIN7都自带截屏软件,甚至OFFICE2010开始都有截屏的功能. 截屏软件虽多,无外乎三种截屏方式:全屏截图.窗口截图.自定义矩形截图. 其中,窗口截图 ...

  5. DXGI快速截屏录屏技术

    DXGI快速截屏录屏技术 概述   很多地方都需要用到截屏/录屏技术,比如桌面直播,桌面录制等等.在微软Windows平台,有很多截屏的接口,不过大多数性能并不理想,Windows8以后微软引入了一套 ...

  6. C#在截屏时将截屏之前需要隐藏的控件也截入

    最近我在项目中遇到一个让我十分头疼的问题,就是我在截屏时也将截屏之前隐藏的控件也截入了. 情况:我在Winform窗体有个截屏功能按钮,实现在调用WPF全屏后截屏,但在截屏WPF界面前将界面里的一个L ...

  7. C语言实现截屏

    实现手机控制电脑执行部分功能需要获取桌面状态,在网上找的一段截屏代码 /** * GDI 截屏函数 * 参数 hwnd 要截屏的窗口句柄 * 参数 dirPath 截图存放目录 * 参数 filena ...

  8. C#使用phantomjs 进行网页整页截屏

    C#使用phantomjs 进行网页整页截屏 hantomjs 是一个基于js的webkit内核无头浏览器 也就是没有显示界面的浏览器,这样访问网页就省去了浏览器的界面绘制所消耗的系统资源,比较适合用 ...

  9. Linux系统安装MySql步骤及截屏

    ➠更多技术干货请戳:听云博客 如下是我工作中的记录,介绍的是linux系统下使用官方编译好的二进制文件进行安装MySql的安装过程和安装截屏,这种安装方式速度快,安装步骤简单! 需要的朋友可以按照如下 ...

  10. Android使用C++截屏并显示

    使用android底层自带的截屏源码进行修改后,将截取屏幕的内容再次显示在屏幕上,使屏幕呈现出暂停的效果. android自带的截屏代码在android\JB\frameworks\base\cmds ...

随机推荐

  1. 动态规划(六)——树形dp

    树形dp,又称树状dp,即在树上进行的dp,在设计动态规划算法时,一般就以节点从深到浅(子树从小到大)的顺序作为dp的"阶段",dp的状态表示中,第一维通常是节点编号(代表以该节点 ...

  2. 嘉楠k210 多线程 点亮流水灯

    from Maix import GPIO from fpioa_manager import fm import _thread import time fm.register(24, fm.fpi ...

  3. 力扣693(java)-交替位二进制数(简单)

    题目: 给定一个正整数,检查它的二进制表示是否总是 0.1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同. 示例 1: 输入:n = 5输出:true解释:5 的二进制表示是:101示例 ...

  4. 创新推出 | Serverless 场景排查问题利器:函数实例命令行操作

    ​简介: 实例命令行功能的推出希望能消除用户使用 Serverless 的"最后一公里",直接将真实的函数运行环境展现给用户,此后 Serverless 将不再是一个"黑 ...

  5. 如何高效完成ECS多环境部署?

    ​简介:通过本文,你可以了解到,如何通过云效流水线有效拉通开发与运维,打破二者之间的壁垒墙,让开发与运维高效联动.在软件开发和部署过程中,我们的软件往往需要在不同的运行环境中运行,例如:开发人员本地开 ...

  6. 如何避免 Go 命令行执行产生“孤儿”进程?

    简介: 在 Go 程序当中,如果我们要执行命令时,通常会使用 exec.Command ,也比较好用,通常状况下,可以达到我们的目的,如果我们逻辑当中,需要终止这个进程,则可以快速使用 cmd.Pro ...

  7. MongoDB 5.0新特性概览

    ​简介: MongoDB 5.0标志着一个新的发布周期的到来,以更快地交付新特性给到用户.版本化API与在线重新分片相结合,使用户不必担心未来的数据库升级以及业务变化问题:本地原生时间序列数据平台也使 ...

  8. 在线运行代码的 PHP 沙盒环境实现

      演示: 多版本PHP运行代码 作用: 方便作为独立的调试环境运行 一些 临时逻辑,查看执行结果. 方便比较不同版本的 PHP 执行差异,进行一般的兼容性测试. 思路: Docker镜像构建多个PH ...

  9. dotnet C# 只创建对象不调用构造函数方法

    有时我期望只是创建出对象,但是不要调用对象的构造方法,可以通过使用 FormatterServices 的 GetUninitializedObject 函数来实现只创建对象不调用构造函数方法 这个 ...

  10. .net 记录http请求

    记录http请求 环境 .net7 一.过滤器(Filter) 这个过程用的的是操作过滤器(ActionFilter) 二. 2.1 继承IAsyncActionFilter 2.2 重写OnActi ...