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. HarmonyOS分布式文件系统开发指导

      分布式文件系统概述 分布式文件系统(hmdfs,HarmonyOS Distributed File System)提供跨设备的文件访问能力,适用于如下场景: ● 两台设备组网,用户可以利用一台设 ...

  2. nginx重新整理——————http请求的11个阶段中的find_config[十三]

    前言 简单介绍一下find_config 与 preaccess 阶段. 正文 find_config 很大一部分工作是进行location的匹配. 来一张图看下location指令和merge_sl ...

  3. 重新点亮linux 命令树————用户和用户组的配置文件[八]

    前言 简单整理一下 正文 首先看下vim /etc/passwd 这个东西. 可以看到这些就是我们的用户表. 刚才我们创建的user1就在末尾了. 那么下面有这个x:1001:1001 这个是什么意思 ...

  4. 力扣507(java)-完美数(简单)

    题目: 对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」. 给定一个 整数 n, 如果是完美数,返回 true:否则返回 false. 示例 1: 输入:num ...

  5. 运营也用的起来的数据分析工具:Quick BI即席分析详解

    ​简介: 数据部门是一个容易被投诉的"高危"部门,需求响应慢.数据准确性不高会影响业务的发展. 然而数据分析师每周动辄就有几十个需求在手,无限的加班也无法解决所有问题,到底怎样才能 ...

  6. [FAQ] Truffle Deployer 合约传参问题: Invalid number of parameters for "undefined". Got 0 expected 1!

    在使用 `truffle migrate` 时,如果合约的构造函数需要传参,而部署脚本里没有传的时候,就会报这个错. 未传参时: const Migrations = artifacts.requir ...

  7. [FE] Chrome Extension 五步曲

    1. Create the manifest.jsonOnly three fields is needed. { "name": "Getting Started Ex ...

  8. dotnet win32 使用 WIC 获取系统编解码器

    在 Windows 系统上,有一个很重要的概念是 Windows Imaging Component 也就是 WIC 层,这是专门用来处理多媒体相关的系统组件,特别是用来处理图片相关,包括编码和解码和 ...

  9. Ubuntu环境下docker每次都需要sudo的问题

    1.添加 docker 用户组 sudo groupadd docker 可以通过 cat /etc/group 指令查看存在的用户组 2.将当前用户添加到 docker 组中 sudo gpassw ...

  10. Web Audio API 第6章 高级主题

    高级主题 这一章涵盖了非常重要的主题,但比本书的其他部分稍微复杂一些. 我们会深入对声音添加音效,完全不通过任何音频缓冲来计算合成音效, 模拟不同声音环境的效果,还有关于空 3D 空间音频. 重要理论 ...