C#?wpf使用GDI+實(shí)現(xiàn)截屏效果
前言
wpf做屏幕錄制或者屏幕廣播之類的功能時(shí)需要實(shí)現(xiàn)截屏,在C#中比較容易實(shí)現(xiàn)的截屏方法是使用GDI+,本文將展示使用GDI+截屏的具體實(shí)現(xiàn)方案,包括如何繪制鼠標(biāo),按幀率采集屏幕、將GDI+對(duì)象轉(zhuǎn)成wpf對(duì)象等。
一、引用System.Drawing
在wpf中使用GDI+功能需要引入System.Drawing庫(kù),有2種方式:在.net framework中直接引用系統(tǒng)庫(kù)即可。在.net core中可以引用mono實(shí)現(xiàn)的跨平臺(tái)的System.Drawing,提供接口與系統(tǒng)程序集是一模一樣的,而且性能略好一些。
方法一、引用系統(tǒng)程序集
1、右鍵引用

2、搜索drawing,勾選后確定即可。

方法二、NuGet獲取跨平臺(tái)Drawing
在.net core中無法引用系統(tǒng)的Drawing,只能通過Nuget獲取跨平臺(tái)Drawing。1、右鍵引用打開NuGet界面

2、搜索drawing并安裝

二、實(shí)現(xiàn)截屏
1.簡(jiǎn)單截屏
簡(jiǎn)單的截屏只需幾行代碼即可實(shí)現(xiàn):
/// <summary>
/// 截取一幀圖片
/// </summary>
/// <param name="x">x坐標(biāo)</param>
/// <param name="y">y坐標(biāo)</param>
/// <param name="width">寬</param>
/// <param name="height">高</param>
/// <returns>截屏后的位圖對(duì)象,需要調(diào)用Dispose手動(dòng)釋放資源。</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.繪制鼠標(biāo)
上述方式實(shí)現(xiàn)的截屏是沒有鼠標(biāo)的,如果要顯示鼠標(biāo)則需要我們手動(dòng)繪制,通過獲取鼠標(biāo)的icon繪制到背景圖像中。繪制鼠標(biāo)需要用到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 (異或光標(biāo))
{
使用gdi的rop繪制
}
else
{
using (var icon = System.Drawing.Icon.FromHandle(ci.hCursor))
{
graphics.DrawIcon(icon, mouseX, mouseY);
}
}
}
}3.轉(zhuǎn)換成wpf對(duì)象
參考我的另一篇文章《C# wpf Bitmap轉(zhuǎn)換成WriteableBitmap(BitmapSource)的方法》
4.屏幕采集
基于上面的實(shí)現(xiàn)加上開線程及循環(huán)截屏就可以做到屏幕采集了。示例代碼如下:
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);
//繪制鼠標(biāo)
...
//繪制鼠標(biāo)--end
//將位圖數(shù)據(jù)寫入wpf對(duì)象、編碼推流等
...
//將位圖數(shù)據(jù)寫入wpf對(duì)象、編碼推流等--end
Thread.Sleep(幀率延時(shí));
}
}三、完整代碼
通過上述方法得到的接口設(shè)計(jì)如下(不含具體實(shí)現(xiàn)):
/// <summary>
/// 截屏事件參數(shù)
/// </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>
/// 截屏數(shù)據(jù)事件參數(shù)
/// </summary>
public class ScreenCaptureDataEventArgs : ScreenCaptureEventArgs
{
/// <summary>
/// 圖像數(shù)據(jù)
/// </summary>
public IntPtr Data { set; get; }
/// <summary>
/// 數(shù)據(jù)長(zhǎng)度
/// </summary>
public int Length { set; get; }
/// <summary>
/// 一行數(shù)據(jù)長(zhǎng)度
/// </summary>
public int Stride { set; get; }
}
/// <summary>
/// 數(shù)值類型
/// </summary>
public enum ScreenCaptureValueType
{
/// <summary>
/// 實(shí)際值
/// </summary>
TrueValue,
/// <summary>
/// 按比例計(jì)算
/// </summary>
RadioValue
}
/// <summary>
/// 截屏對(duì)象
/// </summary>
public class ScreenCapture
{
/// <summary>
/// 截屏事件,每截取一幀都會(huì)回調(diào)
/// </summary>
public event EventHandler<ScreenCaptureDataEventArgs> Captured;
/// <summary>
/// 截屏開始時(shí)回調(diào)
/// </summary>
public event EventHandler<ScreenCaptureEventArgs> Started;
/// <summary>
/// 結(jié)束時(shí)回調(diào)
/// </summary>
public event EventHandler Stoped;
/// <summary>
/// 截屏是否已停止
/// </summary>
public bool IsStoped { private set; get; }
/// <summary>
/// 是否截取鼠標(biāo)
/// </summary>
public bool IsPaintMouse { set; get; } = true;
/// <summary>
/// 截屏區(qū)域的計(jì)算方式
/// TrueValue為實(shí)際值。RatioValue為比例值,范圍0-1,全屏設(shè)為0,0,1,1,則無論任何設(shè)備任何分辨率都是截取全屏。
/// </summary>
public ScreenCaptureValueType ClipRectValueType { private set; get; } = ScreenCaptureValueType.RadioValue;
/// <summary>
/// 截屏區(qū)域X坐標(biāo)
/// </summary>
public double ClipX { private set; get; } = 0;
/// <summary>
/// 截屏區(qū)域Y坐標(biāo)
/// </summary>
public double ClipY { private set; get; } = 0;
/// <summary>
/// 截屏區(qū)域?qū)?
/// </summary>
public double ClipWidth { private set; get; } = 1;
/// <summary>
/// 截屏區(qū)域高
/// </summary>
public double ClipHeight { private set; get; } = 1;
/// <summary>
/// 截屏幀率
/// </summary>
public double Framerate{ set; get; }=30;
/// <summary>
/// 設(shè)置截屏區(qū)域
/// </summary>
/// <param name="x">x坐標(biāo)</param>
/// <param name="y">y坐標(biāo)</param>
/// <param name="width">寬</param>
/// <param name="height">高</param>
/// <param name="valueType">TrueValue為實(shí)際值。RatioValue為比例值,范圍0-1,全屏設(shè)為0,0,1,1,則無論任何設(shè)備任何分辨率都是截取全屏。</param>
public void SetClipRect(double x, double y, double width, double height, ScreenCaptureValueType valueType);
/// <summary>
/// 啟動(dòng)屏幕采集
/// </summary>
public void Start();
/// <summary>
/// 停止屏幕采集
/// 異步方法,Stoped事件為真正的停止。
/// </summary>
public void Stop();
/// <summary>
/// 截取一幀圖片
/// </summary>
/// <param name="x">x坐標(biāo)</param>
/// <param name="y">y坐標(biāo)</param>
/// <param name="width">寬</param>
/// <param name="height">高</param>
/// <param name="isPaintMouse">是否繪制鼠標(biāo)</param>
/// <returns>截屏后的位圖對(duì)象,需要調(diào)用Dispose手動(dòng)釋放資源。</returns>
public static System.Drawing.Bitmap Snapshot(int x, int y, int width, int height, bool isPaintMouse);四、使用示例
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();
}效果預(yù)覽:

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();
//注冊(cè)事件
sc.Captured += Sc_Captured;
sc.Started += Sc_Started;
//開始采集
sc.Start();
}
private void Sc_Started(object sender, ScreenCaptureEventArgs e)
{
Dispatcher.Invoke(() =>
{
//初始化位圖對(duì)象
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)
//寬高改變了重新初始化位圖對(duì)象
{
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)
{
//異步的方式退出才不會(huì)造成死鎖
if (!sc.IsStoped)
{
sc.Stop();
sc.Stoped += (s, e) =>
{
Dispatcher.Invoke(() =>
{
Close();
});
};
e.Cancel = true;
}
}
示例二、動(dòng)態(tài)調(diào)整參數(shù)
可以在采集過程中動(dòng)態(tài)調(diào)整參數(shù),比如采集區(qū)域、幀率、鼠標(biāo)繪制。
在示例一的基礎(chǔ)上添加如下代碼:
//測(cè)試動(dòng)態(tài)調(diào)整參數(shù)
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();
//測(cè)試動(dòng)態(tài)調(diào)整參數(shù) --end效果預(yù)覽:

總結(jié)
本文簡(jiǎn)單介紹GDI+截屏的方法,添加鼠標(biāo)的實(shí)現(xiàn)以及將GDI+對(duì)象轉(zhuǎn)換成wpf對(duì)象,和屏幕采集的實(shí)現(xiàn),總的來說不算是特別容易,原理很簡(jiǎn)單但是有不少細(xì)節(jié)需要處理,尤其是調(diào)試中出現(xiàn)資源釋放問題,需要有c++開發(fā)的意識(shí),才能很好的定位和解決問題。
到此這篇關(guān)于C# wpf使用GDI+實(shí)現(xiàn)截屏效果的文章就介紹到這了,更多相關(guān)C# wpf截屏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 使用C#從零開始實(shí)現(xiàn)一個(gè)截屏工具
- 使用C#自制一個(gè)截屏工具
- C#?WPF使用GDI實(shí)現(xiàn)截屏功能
- C# WPF利用Clip屬性實(shí)現(xiàn)截屏框功能
- C#?wpf實(shí)現(xiàn)截屏框熱鍵截屏的示例代碼
- C#?wpf使用DockPanel實(shí)現(xiàn)制作截屏框
- 通過C#編寫一個(gè)簡(jiǎn)易的Windows截屏增強(qiáng)工具
- C#實(shí)現(xiàn)小截屏軟件功能
- 基于C#實(shí)現(xiàn)的屏幕指定區(qū)域截屏代碼
- c#不使用系統(tǒng)api實(shí)現(xiàn)可以指定區(qū)域屏幕截屏功能
- c#根據(jù)網(wǎng)址抓取網(wǎng)頁截屏生成圖片的示例
- C# 全屏截圖的功能實(shí)現(xiàn)
相關(guān)文章
用序列化實(shí)現(xiàn)List<T> 實(shí)例的深復(fù)制(推薦)
下面小編就為大家?guī)硪黄眯蛄谢瘜?shí)現(xiàn)List<T> 實(shí)例的深復(fù)制(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
C#動(dòng)態(tài)獲取系統(tǒng)當(dāng)前日期與時(shí)間的方法詳解
在C#編程中,動(dòng)態(tài)獲取系統(tǒng)當(dāng)前的日期和時(shí)間是一項(xiàng)基礎(chǔ)而關(guān)鍵的操作,文詳細(xì)介紹了?DateTime.Now?、?DateTime.Today?和?DateTime.UtcNow?等常用屬性,并結(jié)合示例代碼演示了如何獲取和格式化當(dāng)前時(shí)間,希望對(duì)大家有所幫助2025-11-11
C#中DateTime的格式符的實(shí)現(xiàn)示例
本文介紹了C#中DateTime格式符的使用方法,分為預(yù)定義格式和自定義格式兩類,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-12-12
如何用C#在PC上查找連接藍(lán)牙設(shè)備并實(shí)現(xiàn)數(shù)據(jù)傳輸
這篇文章主要介紹了如何用C#在PC上查找連接藍(lán)牙設(shè)備并實(shí)現(xiàn)數(shù)據(jù)傳輸,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03
C#實(shí)現(xiàn)簡(jiǎn)單的雙色球抽取中獎(jiǎng)號(hào)碼代碼
這篇文章主要介紹了C#實(shí)現(xiàn)簡(jiǎn)單的雙色球抽取中獎(jiǎng)號(hào)碼代碼的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

