C#?wpf使用GDI+實(shí)現(xiàn)截屏效果
前言
wpf做屏幕錄制或者屏幕廣播之類的功能時(shí)需要實(shí)現(xiàn)截屏,在C#中比較容易實(shí)現(xiàn)的截屏方法是使用GDI+,本文將展示使用GDI+截屏的具體實(shí)現(xiàn)方案,包括如何繪制鼠標(biāo),按幀率采集屏幕、將GDI+對象轉(zhuǎn)成wpf對象等。
一、引用System.Drawing
在wpf中使用GDI+功能需要引入System.Drawing庫,有2種方式:在.net framework中直接引用系統(tǒng)庫即可。在.net core中可以引用mono實(shí)現(xiàn)的跨平臺的System.Drawing,提供接口與系統(tǒng)程序集是一模一樣的,而且性能略好一些。
方法一、引用系統(tǒng)程序集
1、右鍵引用
2、搜索drawing,勾選后確定即可。
方法二、NuGet獲取跨平臺Drawing
在.net core中無法引用系統(tǒng)的Drawing,只能通過Nuget獲取跨平臺Drawing。1、右鍵引用打開NuGet界面
2、搜索drawing并安裝
二、實(shí)現(xiàn)截屏
1.簡單截屏
簡單的截屏只需幾行代碼即可實(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>截屏后的位圖對象,需要調(diào)用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.繪制鼠標(biāo)
上述方式實(shí)現(xiàn)的截屏是沒有鼠標(biāo)的,如果要顯示鼠標(biāo)則需要我們手動繪制,通過獲取鼠標(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對象
參考我的另一篇文章《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對象、編碼推流等 ... //將位圖數(shù)據(jù)寫入wpf對象、編碼推流等--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ù)長度 /// </summary> public int Length { set; get; } /// <summary> /// 一行數(shù)據(jù)長度 /// </summary> public int Stride { set; get; } } /// <summary> /// 數(shù)值類型 /// </summary> public enum ScreenCaptureValueType { /// <summary> /// 實(shí)際值 /// </summary> TrueValue, /// <summary> /// 按比例計(jì)算 /// </summary> RadioValue } /// <summary> /// 截屏對象 /// </summary> public class ScreenCapture { /// <summary> /// 截屏事件,每截取一幀都會回調(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> /// 啟動屏幕采集 /// </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>截屏后的位圖對象,需要調(diào)用Dispose手動釋放資源。</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(); //注冊事件 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; } }
示例二、動態(tài)調(diào)整參數(shù)
可以在采集過程中動態(tài)調(diào)整參數(shù),比如采集區(qū)域、幀率、鼠標(biāo)繪制。
在示例一的基礎(chǔ)上添加如下代碼:
//測試動態(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(); //測試動態(tài)調(diào)整參數(shù) --end
效果預(yù)覽:
總結(jié)
本文簡單介紹GDI+截屏的方法,添加鼠標(biāo)的實(shí)現(xiàn)以及將GDI+對象轉(zhuǎn)換成wpf對象,和屏幕采集的實(shí)現(xiàn),總的來說不算是特別容易,原理很簡單但是有不少細(xì)節(jié)需要處理,尤其是調(diào)試中出現(xiàn)資源釋放問題,需要有c++開發(fā)的意識,才能很好的定位和解決問題。
到此這篇關(guān)于C# wpf使用GDI+實(shí)現(xiàn)截屏效果的文章就介紹到這了,更多相關(guān)C# wpf截屏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入分析C#連接Oracle數(shù)據(jù)庫的連接字符串詳解
本篇文章是對C#連接Oracle數(shù)據(jù)庫的連接字符串進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C# WinForm制作登錄界面的實(shí)現(xiàn)步驟
本文主要介紹了C# WinForm制作登錄界面的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05C# 使用WPF 用MediaElement控件實(shí)現(xiàn)視頻循環(huán)播放
在WPF里用MediaElement控件,實(shí)現(xiàn)一個(gè)循環(huán)播放單一視頻的程序,同時(shí)可以控制視頻的播放、暫停、停止。這篇文章給大家介紹了C# 使用WPF 用MediaElement控件實(shí)現(xiàn)視頻循環(huán)播放,需要的朋友參考下吧2018-04-04C#實(shí)現(xiàn)注冊碼注冊機(jī)制效果詳解
這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)注冊碼注冊機(jī)制效果,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2023-01-01C#調(diào)用百度API實(shí)現(xiàn)活體檢測的方法
這篇文章主要給大家介紹了關(guān)于C#調(diào)用百度API實(shí)現(xiàn)活體檢測的方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09