文章目錄
- 前言
- 一、參考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;
}
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);
}
}
}
}
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(幀率延時);
}
}
三、完整代碼
通過上述方法得到的介面設計如下(不含具體實作):
/// <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
四、使用示例
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>
cs
public MainWindow()
{
InitializeComponent();
var bm = ScreenCapture.Snapshot(0, 0, 1920, 1080, true);
var wb = BitmapInterop.BitmapToWriteableBitmap(bm);
img.Source = wb;
bm.Dispose();
}
效果預覽:

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>
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++開發的意識,才能很好的定位和解決問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/397500.html
標籤:其他
上一篇:C語言大一課設——不會五子棋
