C# wpf 使用GDI+实现截屏

这篇具有很好参考价值的文章主要介绍了C# wpf 使用GDI+实现截屏。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

wpf截屏系列

第一章 使用GDI+实现截屏(本章)
第二章 使用DockPanel制作截屏框
第三章 实现截屏框热键截屏
第四章 实现截屏框实时截屏
第五章 使用ffmpeg命令行实现录屏



前言

wpf做屏幕录制或者屏幕广播之类的功能时需要实现截屏,在C#中比较容易实现的截屏方法是使用GDI+,本文将展示使用GDI+截屏的具体实现方案,包括如何绘制鼠标,按帧率采集屏幕、将GDI+对象转成wpf对象等。


一、引用System.Drawing

在wpf中使用GDI+功能需要引入System.Drawing库,有2种方式:在.net framework中直接引用系统库即可。在.net core中可以引用mono实现的跨平台的System.Drawing,提供接口与系统程序集是一模一样的,而且性能略好一些。

方法一、引用系统程序集

1、右键引用
C# wpf 使用GDI+实现截屏
2、搜索drawing,勾选后确定即可。
C# wpf 使用GDI+实现截屏

方法二、NuGet获取跨平台Drawing

在.net core中无法引用系统的Drawing,只能通过Nuget获取跨平台Drawing。
1、右键引用打开NuGet界面
C# wpf 使用GDI+实现截屏
2、搜索drawing并安装
C# wpf 使用GDI+实现截屏


二、实现截屏

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();
}

效果预览:
C# wpf 使用GDI+实现截屏

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;
    }
}

C# wpf 使用GDI+实现截屏

示例二、动态调整参数

可以在采集过程中动态调整参数,比如采集区域、帧率、鼠标绘制。
在示例一的基础上添加如下代码:

//测试动态调整参数
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

效果预览:
C# wpf 使用GDI+实现截屏


总结

以上就是今天要讲的内容,本文简单介绍GDI+截屏的方法,添加鼠标的实现以及将GDI+对象转换成wpf对象,和屏幕采集的实现,总的来说不算是特别容易,原理很简单但是有不少细节需要处理,尤其是调试中出现资源释放问题,需要有c++开发的意识,才能很好的定位和解决问题。文章来源地址https://www.toymoban.com/news/detail-469199.html

到了这里,关于C# wpf 使用GDI+实现截屏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【HTML5系列】第一章 · HTML5新增语义化标签

            Hello大家好, 我是【 麟-小白 】,一位 软件工程 专业的学生,喜好 计算机知识 。希望大家能够一起 学习进步 呀!本人是一名 在读大学生 ,专业水平有限,如发现 错误 或 不足之处 ,请多多指正!谢谢大家!!!         如果 小哥哥小姐姐们 对我的文章感兴趣

    2024年02月04日
    浏览(64)
  • AIGC系列文章目录 第一章 AIGC 与AI对话,如何写好prompt?

    生成式人工智能AIGC(Artificial Intelligence Generated Content)是人工智能1.0时代进入2.0时代的重要标志。 AIGC对于人类社会、人工智能的意义是里程碑式的。 短期来看 AIGC改变了基础的生产力工具, 中期来看 会改变社会的生产关系, 长期来看 促使整个社会生产力发生质的突破,在

    2024年02月06日
    浏览(46)
  • 如何洞察 C# 程序的 GDI 句柄泄露

    前段时间有位朋友找到我,说他的程序界面操作起来很慢并且卡顿等一些不正常现象,从任务管理器看了下 GDI句柄 已经到 1w 了,一时也找不出什么代码中哪里有问题,让我帮忙看下,其实这种问题看内存dump作用不是很大,主要是写脚本很麻烦,这一篇我们就来简单聊聊如何

    2024年02月08日
    浏览(84)
  • 前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第一章 技术栈简介 (开篇)

    旨在帮助初学者掌握使用现代前端技术栈构建应用的基础知识和技能。在这个系列中,我们将深入探讨如何结合Vue.js、Vite、TypeScript、Pinia和Sass这些强大的工具和框架来开发现代化的前端应用。 通过这个系列,我们将从零开始构建一个完整的前端项目,覆盖项目初始化、组件

    2024年02月05日
    浏览(52)
  • 锁相环技术原理及FPGA实现(第一章1.1)

            锁相环技术具有很强的专业性,要掌握其工作原理,透彻理解各种设计方法和思路,最终游刃有余地设计出性能优良的锁相环电路,首先需要掌握一系列相关工具。请注意,不是“一种”工具,而是“一系列”工具: FPGA 开发软件 Quartus II、 HDL 仿真软件 ModelSim、

    2024年02月03日
    浏览(36)
  • 锁相环技术原理及FPGA实现(第一章1.2)

    4)嵌入式块 RAM( BRAM)         大多数 FPGA 都具有内嵌的 BRAM, 这大大拓展了 FPGA 的应用范围和灵活性。 块 RAM 可被配置为单端口 RAM、双端口 RAM、地址存储器( CAM),以及 FIFO 等常用存储结构。 CAM 存储器在其内部的每个存储单元中都有一个比较逻辑,写入 CAM 中的数据

    2024年02月03日
    浏览(43)
  • C++ 使用 GDI+ 实现格式为bmp的图像内存转为jpg格式,并且可以实现旋转保存

    将获取的bmp内存数据,经过转换成jpg格式,根据自己的需求可以对图像先做旋转,再进行保存,主要用到的是GDI+工具。 函数的说明 /// /获取的图像内存数据 /// /图像的像素位数 /// /图像的宽度 /// /图像的高度 /// /图像保存的路径 /// /图像保存的路径加图像名称 ///

    2024年02月16日
    浏览(44)
  • 使用 Python 和 Pygame 制作游戏:第一章到第五章

    原文: inventwithpython.com/pygame/chapter1.html 译者:飞龙 协议:CC BY-NC-SA 4.0 在阅读本书之前,如果您了解一些 Python 编程知识(或者知道如何使用 Python 之外的其他语言进行编程),可能会有所帮助;但是即使您没有,您仍然可以阅读本书。编程并不像人们想象的那么难。如果您遇

    2024年01月17日
    浏览(81)
  • 在.NET Framework中使用RocketMQ(阿里云版)实战【第一章】

    章节 第一章:https://www.cnblogs.com/kimiliucn/p/17662052.html 第二章:https://www.cnblogs.com/kimiliucn/p/17667200.html 作者:西瓜程序猿 主页传送门:https://www.cnblogs.com/kimiliucn/ 在开发某一个需求的时候,领导要求使用RocketMQ(阿里云版) 作为消息队列。使用的版本是5.x,目前也已经没有4.x购买的

    2024年02月11日
    浏览(38)
  • 基于linux下的高并发服务器开发(第一章)- 模拟实现 ls-l 命令

     这一小节会用到上面两张图的红色框里面的变量 任务: 模拟实现 ls -l 指令 -rw-rw-r-- 1 nowcoder nowcoder 12 12月  3 15:48 a.txt    

    2024年02月16日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包