赞
踩
第一章 使用GDI+实现截屏(本章)
第二章 使用DockPanel制作截屏框
第三章 实现截屏框热键截屏
第四章 实现截屏框实时截屏
第五章 使用ffmpeg命令行实现录屏
wpf做屏幕录制或者屏幕广播之类的功能时需要实现截屏,在C#中比较容易实现的截屏方法是使用GDI+,本文将展示使用GDI+截屏的具体实现方案,包括如何绘制鼠标,按帧率采集屏幕、将GDI+对象转成wpf对象等。
在wpf中使用GDI+功能需要引入System.Drawing库,有2种方式:在.net framework中直接引用系统库即可。在.net core中可以引用mono实现的跨平台的System.Drawing,提供接口与系统程序集是一模一样的,而且性能略好一些。
1、右键引用
2、搜索drawing,勾选后确定即可。
在.net core中无法引用系统的Drawing,只能通过Nuget获取跨平台Drawing。
1、右键引用打开NuGet界面
2、搜索drawing并安装
简单的截屏只需几行代码即可实现:
/// <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; }
上述方式实现的截屏是没有鼠标的,如果要显示鼠标则需要我们手动绘制,通过获取鼠标的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); } } } }
参考我的另一篇文章《C# wpf Bitmap转换成WriteableBitmap(BitmapSource)的方法》
基于上面的实现加上开线程及循环截屏就可以做到屏幕采集了。示例代码如下:
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(帧率延时);
}
}
通过上述方法得到的接口设计如下(不含具体实现):
/// <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);
完整代码如下:
https://download.csdn.net/download/u013113678/71984470
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>
cs
public MainWindow()
{
InitializeComponent();
var bm = ScreenCapture.Snapshot(0, 0, 1920, 1080, true);
var wb = BitmapInterop.BitmapToWriteableBitmap(bm);
img.Source = wb;
bm.Dispose();
}
效果预览:
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>
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; } }
可以在采集过程中动态调整参数,比如采集区域、帧率、鼠标绘制。
在示例一的基础上添加如下代码:
//测试动态调整参数 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
效果预览:
以上就是今天要讲的内容,本文简单介绍GDI+截屏的方法,添加鼠标的实现以及将GDI+对象转换成wpf对象,和屏幕采集的实现,总的来说不算是特别容易,原理很简单但是有不少细节需要处理,尤其是调试中出现资源释放问题,需要有c++开发的意识,才能很好的定位和解决问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。