.NetCore录屏生成Gif动图程序(Form)的开发过程[代码已上传GitCode]

news2024/11/22 6:36:22

🌮.NetCore录屏生成Gif动图程序(Form)的开发过程

前言:

开发环境:.NetCore3.1

GitCode地址:罗马苏丹默罕默德 / RecordDesktopGif · GitCode

FrameWork版本地址:尚未同步功能

实现功能

  • 选中屏幕的一块矩形区域按照设置的参数录制生成Gif动图

🌀一,截取多张图片作为帧生成Gif

⛵️思路:

和视频一样,Gif同样是由多张大小一样的图片按帧进行播放,只需要多次截图屏幕(的区域),再按时间顺序合成,则可得到录屏的Gif

使用工具:ImageSharp和System.Drawing(Tip:两个组件中有一些同名的类,需要使用全限定的类名)

代码实现

int fps = 20;   //每秒截图的数量

int Secs = 3;   //持续录制的时长

int INCR = 1;   

for (int i = 1; i <= Secs* fps; i++)
{
    CaptureScreenArea(0, 0, 200, 200, ref INCR); //截取屏幕左上角200x200的区域
    Thread.Sleep(50);    //请注意这里SleepTimex(Secs*fps) = Secs(秒)
    					 //Sleep时间和fps的公式为SleepTime = 1000/fps(毫秒)
}
//用一个数组加载上面保存再TempDir文件夹下的截图
SixLabors.ImageSharp.Image[] imgs = new SixLabors.ImageSharp.Image[Secs* fps];

for (int i = 0; i < Secs* fps; i++)
{
    imgs[i] = SixLabors.ImageSharp.Image.Load($@"TempDir/{i+1}.png");
}

//创建Gif对象
Image<Rgba32> gif = new Image<Rgba32>(200, 200);
//获取Gif头帧的Meta信息
GifFrameMetadata meta = gif.Frames.RootFrame.Metadata.GetGifMetadata();

meta.FrameDelay = 0;//设置头帧开始播放的延迟

foreach (SixLabors.ImageSharp.Image im in imgs)
{	//设置加载图片的Gif格式的Meta信息
    GifFrameMetadata temp_meta = im.Frames.RootFrame.Metadata.GetGifMetadata();
    temp_meta.FrameDelay = 10;//梁振之前的间隔时间:(0~100,数值越小越快)

    gif.Frames.AddFrame(im.Frames.RootFrame); //添加到Gif中
}


gif.Metadata.GetGifMetadata().RepeatCount = 0; //设置重复次数,0则一直循环
gif.SaveAsGif("holyshit.gif");

Directory.Delete("TempDir", true);
 public static void CaptureScreenArea(int Left,int Top,int Length,int Width,ref int INCR)
{
    Bitmap bitmap = new Bitmap(Length, Width);
	//通过参数选择屏幕的区域
    System.Drawing.Rectangle rect = new System.Drawing.Rectangle(Left, Top, Length, Width);

    if (Directory.Exists("TempDir"))
    {
        using(Graphics g = Graphics.FromImage(bitmap))
        {
            g.CopyFromScreen(rect.Left, rect.Top, 0, 0, rect.Size);
        }
        //保存到上面的临时文件夹中
        bitmap.Save($@"TempDir/{INCR}.png", System.Drawing.Imaging.ImageFormat.Png);
    }
    else
    {
        Directory.CreateDirectory("TempDir");
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.CopyFromScreen(rect.Left, rect.Top, 0, 0, rect.Size);
        }
       
        bitmap.Save($@"TempDir/{INCR}.png", System.Drawing.Imaging.ImageFormat.Png);
    }
    INCR++;
}

🍨 二,抽离并保存需要的参数

🌵保存方式:

  • 保存在文件中(.config/.xml/.properties)
  • 保存在数据库

例中需要保存的数据很少,故应保存在项目下的文件中。

🈂️Tip:这里我使用了.properties文件,但.Net默认不支持.properties格式的,需要先获取Util等工具来存取。

public class PropertiesUtil
{
    public static Hashtable Load(string file)
    {
        Hashtable ht = new Hashtable(16);
        string content = null;
        try
        {
            content = File.ReadAllText(file, System.Text.Encoding.UTF8);
        }
        catch (Exception e)
        {
            return null;
        }
        string[] rows = content.Split('\n');
        string[] kv = null;
        foreach (string c in rows)
        {
            if (c.Trim().Length == 0)
                continue;
            kv = c.Split('=');
            if (kv.Length == 1)
            {
                ht[kv[0].Trim()] = "";
            }
            else if (kv.Length == 2)
            {
                ht[kv[0].Trim()] = kv[1].Trim();
            }
        }
        return ht;
    }

    public static bool Save(string file, Hashtable ht)
    {
        if (ht == null || ht.Count == 0)
            return false;
        StringBuilder sb = new StringBuilder(ht.Count * 12);
        foreach (string k in ht.Keys)
        {
            sb.Append(k).Append('=').Append(ht[k]).Append(System.Environment.NewLine);
        }
        try
        {
            File.WriteAllText(file, sb.ToString(), System.Text.Encoding.UTF8);
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }


}

请添加图片描述

🍏 三,创建Form获取选择的目标区域的Rectangle信息

🍶思路:

Tip:参考Win10+Shift+s的截图样式,拉取一个框定区域的方法也可以满足Gif框定屏幕区的作用域。

这类/类截图代码在WinForm的资料中有很多,以下为项目中的框选代码。

 public partial class ScreenBody : Form
{
    public ScreenBody()
    {
        this.Load += ScreenBody_Load;
        InitializeComponent();
        this.MouseDoubleClick += ScreenBody_MouseDoubleClick;
        this.MouseDown += ScreenBody_MouseDown;
        this.MouseUp += ScreenBody_MouseUp;
        this.MouseMove += ScreenBody_MouseMove;
    }

    private void InitializeComponent()
    {
        this.SuspendLayout();
        // 
        // ScreenBody
        // 
        this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
        this.ClientSize = new System.Drawing.Size(284, 261);
        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        this.Name = "ScreenBody";
        this.Opacity = 0.5D;  //一定要设置背景透明
        this.ResumeLayout(false);

    }

    private Graphics MainPainter;   //主画面
    private Pen pen;                //画笔
    private bool isDowned;          //判断鼠标是否按下 
    private bool RectReady;         //矩形是否绘制完成 
    private Image baseImage;        //基本图形(原来的画面) 
    private Rectangle Rect;         //就是要保存的矩形 
    private Point downPoint;        //鼠标按下的点 
    int tmpx;
    int tmpy;
    //加载初始化
    private void ScreenBody_Load(object sender, EventArgs e)
    {
        this.WindowState = FormWindowState.Maximized;
        MainPainter = this.CreateGraphics();
        pen = new Pen(Brushes.Blue);
        isDowned = false;
        baseImage = this.BackgroundImage;
        Rect = new Rectangle();
        RectReady = false;
    }
    //双击保存
    private void ScreenBody_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left && Rect.Contains(e.X, e.Y))
        {
            Image memory = new Bitmap(Rect.Width, Rect.Height);
            Graphics g = Graphics.FromImage(memory);
            g.CopyFromScreen(Rect.X + 1, Rect.Y + 1, 0, 0, Rect.Size);
            //将区域拿到主方法中
            Program.TheFinalRect = Rect;
            Console.WriteLine("Left:"+Rect.Left+" |Top:"+Rect.Top+"|长宽"+Rect.Height+"/"+Rect.Width);

            this.Dispose();
        }
    }
    //左击开始截图或移动,右击撤销
    private void ScreenBody_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            isDowned = true;

            if (RectReady == false)
            {
                Rect.X = e.X;
                Rect.Y = e.Y;
                downPoint = new Point(e.X, e.Y);
            }
            if (RectReady == true)
            {
                tmpx = e.X;
                tmpy = e.Y;
            }
        }
        if (e.Button == MouseButtons.Right)
        {
            this.Close();
            return;
        }
    }
    //左键放开,截图方框完成
    private void ScreenBody_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            isDowned = false;
            RectReady = true;
        }
    }
    //鼠标移动,画框或者拖动
    private void ScreenBody_MouseMove(object sender, MouseEventArgs e)
    {
        if (RectReady == false)
        {
            if (isDowned == true)
            {
                Image New = DrawScreen((Image)baseImage.Clone(), e.X, e.Y);
                MainPainter.DrawImage(New, 0, 0);
                New.Dispose();
            }
        }
        if (RectReady == true)
        {
            if (Rect.Contains(e.X, e.Y))
            {
                this.Cursor = Cursors.Hand;
                if (isDowned == true)
                {
                    //和上一次的位置比较获取偏移量 
                    Rect.X = Rect.X + e.X - tmpx;
                    Rect.Y = Rect.Y + e.Y - tmpy;
                    //记录现在的位置 
                    tmpx = e.X;
                    tmpy = e.Y;
                    MoveRect((Image)baseImage.Clone(), Rect);
                }
            }
            else
            {
                this.Cursor = Cursors.Arrow;
            }
        }
    }
    //画屏幕
    private Image DrawScreen(Image back, int Mouse_x, int Mouse_y)
    {
        Graphics Painter = Graphics.FromImage(back);
        DrawRect(Painter, Mouse_x, Mouse_y);
        return back;
    }
    //画矩形
    private void DrawRect(Graphics Painter, int Mouse_x, int Mouse_y)
    {
        int width = 0;
        int heigth = 0;
        try
        {
            if (Mouse_y < Rect.Y)
            {
                Rect.Y = Mouse_y;
                heigth = downPoint.Y - Mouse_y;
            }
            else
            {
                heigth = Mouse_y - downPoint.Y;
            }
            if (Mouse_x < Rect.X)
            {
                Rect.X = Mouse_x;
                width = downPoint.X - Mouse_x;
            }
            else
            {
                width = Mouse_x - downPoint.X;
            }
        }
        catch (Exception ee)
        {
            MessageBox.Show("cuo");
        }
        finally
        {
            Rect.Size = new Size(width, heigth);
            Painter.DrawRectangle(pen, Rect);
        }
    }

    //移动矩形
    private void MoveRect(Image image, Rectangle Rect)
    {
        Graphics Painter = Graphics.FromImage(image);
        Painter.DrawRectangle(pen, Rect.X, Rect.Y, Rect.Width, Rect.Height);
        MainPainter.DrawImage(image, 0, 0);
        image.Dispose();
    }


}

🏮 四,在主Form任意组件上设置监听函数完成录屏

最后一步则比较随意,可以绑定到按钮上或是右键选项上

private void RecordGIF(object sender,EventArgs e)
{
    if (data["savepath"] == null || !Directory.Exists(data["savepath"].ToString())){
        MessageBox.Show("保存路径不存在,请重新设置保存路径", "FilePathNotFound保存路径不存在", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }


    System.Drawing.Image img = new Bitmap(Width = Screen.AllScreens[0].Bounds.Width, Height = Screen.AllScreens[0].Bounds.Height);

    using (Graphics g = Graphics.FromImage(img))
    {
        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
        using (SolidBrush brush = new(System.Drawing.Color.FromArgb(128,45,89,124)))
        {

           g.FillRectangle(brush, 0, 0, Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);

        }
    }

    ScreenBody clip = new ScreenBody();


    clip.BackgroundImage = img;
    clip.ShowDialog();
    //阻塞主线程直到框出区域

    ClipForm progresser = new ClipForm();

    progresser.ShowDialog();

    System.Drawing.Rectangle rect = Program.TheFinalRect;   //拿到选区的rect信息

    int fps = Convert.ToInt32(data["fps"]);     //秒/帧

    int delay = Convert.ToInt32(data["delay"]); //gif延迟

    decimal Secs = Convert.ToDecimal(data["holdon"]); //持续时间

    int backsec = Convert.ToInt32(data["backsecs"]); //倒数时间

    int INCR = 1;
             

    int RealCount =  (int)(Secs* fps);

    for (int i = 1; i <= RealCount; i++)
    {
        Program.CaptureScreenArea(rect.Left,rect.Top, rect.Width, rect.Height, ref INCR);
        Thread.Sleep(1000/fps);
    }

    SixLabors.ImageSharp.Image[] imgs = new SixLabors.ImageSharp.Image[RealCount];

    for (int i = 0; i < RealCount; i++)
    {
        imgs[i] = SixLabors.ImageSharp.Image.Load($@"TempDir/{i + 1}.png");
    }

    Image<Rgba32> gif = new Image<Rgba32>(rect.Width, rect.Height);

    GifFrameMetadata meta = gif.Frames.RootFrame.Metadata.GetGifMetadata();

    meta.FrameDelay = 0;

    foreach (SixLabors.ImageSharp.Image im in imgs)
    {
        GifFrameMetadata temp_meta = im.Frames.RootFrame.Metadata.GetGifMetadata();
        temp_meta.FrameDelay = delay;

        gif.Frames.AddFrame(im.Frames.RootFrame);
    }


    gif.Metadata.GetGifMetadata().RepeatCount = 0;
    gif.SaveAsGif(data["savepath"]+$@"/RSA{DateTime.Now.ToString("yyyyMMddHHmmss")}.gif");

    Directory.Delete("TempDir", true);

    MessageBox.Show("截取区域完成","success",MessageBoxButtons.OK,MessageBoxIcon.Asterisk);

}

Final:运行截图

请添加图片描述

请添加图片描述
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/753048.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt中QMainWindow的相关介绍

目录 菜单栏 工具栏 状态栏&#xff1a; 停靠窗口&#xff1a; QMainWindow 是标准基础窗口中结构最复杂的窗口&#xff0c;其组成如下: 提供了菜单栏 , 工具栏 , 状态栏 , 停靠窗口 菜单栏&#xff1a;只能有一个&#xff0c;位于窗口的最上方 工具栏&#xff1a;可以有多…

《爱的教育》超全思维导图

思维导图是帮助理解和记忆的高效生产力工具&#xff01; 思维导图以图形的形式表达信息&#xff0c;可视化和关联性&#xff0c;可以更好的激发创作和想象力。 在思维导图中&#xff0c;我们使用简洁的关键词或短语来表达思想&#xff0c;而不是完整的句子或段落。可以帮助我们…

MAYA鲨鱼的绑定

最后一个柚有问题 轴向正确的旋转&#xff0c;成C型 弄乱了 W整体移动 D单个移动 X轴没指向下一个关节 控制器创建 根控制器 控制器很好匹配关节 建组 出来了&#xff0c;控制器位置还在 确保旋转关节是0 处理层级 控制器不跟着 没办法刷蒙皮 # 错误: file: H:/Autodesk/May…

安达发|如何选择适合企业的APS排程系统?

APS是一个优化的排程调度工具&#xff0c;归根结底&#xff0c;APS追求的是企业生产效率的提升&#xff0c;而不是替代人工排程。如何验证呢&#xff1f;对于APS用户来说&#xff0c;检验衡量的最简单的方法就是&#xff0c;拿出过去某一个月实际生产计划的历史数据&#xff0c…

微信小程序创建步骤图文

1.登录微信公众平台 首先&#xff0c;通过网址登录https://mp.weixin.qq.com/&#xff0c;找到立即注册 进行点击。 2.进入该页面时&#xff0c;选择点击小程序 3.注册开发者账号点击立即注册 4.进入该页面 账号信息注册 5.下一步 邮箱激活 注意 &#xff1a;登录邮箱&…

【AntD】Antd Table组件的头部单元格水平居中,单元格居左:

文章目录 一、效果图:二、实现代码: 一、效果图: 二、实现代码: customHeaderCell: () > ({ style: { textAlign: center } }),//头部单元格水平居中

Vue3间距(Space)

可自定义设置以下属性&#xff1a; 对齐方式&#xff08;align&#xff09;&#xff0c;类型&#xff1a;‘start’|‘end’|‘center’|‘baseline’&#xff0c;默认 undefined间距方向&#xff08;direction&#xff09;&#xff0c;类型&#xff1a;‘horizontal’|‘vert…

小程序Url Link跳转怎么获取query参数?

onLoad(options){if (options) {let value1 decodeURIComponent(options.value1)let value2 decodeURIComponent(options.value2)...调用后台接口查询数据} } 我是通过这种方式接收参数的&#xff0c;如果想验证可以通过编译器模拟&#xff1a;

阿里云使用SMC进行服务器迁移

操作文档 阿里云SMC适用于所有的可以公网访问的主机 1、资源准备 1、我们必须要要有相关AliyunSMCFullAccess的权限&#xff0c;如果操作RAM账号具有足够的权限可以自动授权 2、我们的源主机要可以公网访问&#xff0c;并且可以ssh且密码登录 2、在控制台点击迁移源 配置我们源…

Linux Ubuntu安装RabbitMQ服务

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

2023年最新金融银行测试面试题分享(附解析大全)

1、网上银行转账是怎么测的&#xff0c;设计一下测试用例。 回答思路&#xff1a; 宏观上可以从质量模型&#xff08;万能公式&#xff09;来考虑&#xff0c;重点需要测试转账的功能、性能与安全性。设计测试用例可以使用场景法为主&#xff0c;先列出转账的基本流和备选流。…

什么是服务雪崩解决思路

文章目录 1、雪崩问题2、雪崩问题的四种解决思路3、服务保护技术选型对比 1、雪崩问题 假设有一个微服务A&#xff0c;它调用了服务B、服务D&#xff0c;而某时刻服务D挂掉&#xff1a; 服务A要等待服务D的结果&#xff0c;而服务D已经不能正常响应了&#xff0c;此时服务A内部…

基于linux下的高并发服务器开发(第一章)- Makefile(3)1.12

04 / 变量 修改&#xff1a;上一小节的makefile内容 &#xff08;1&#xff09;将如下的 app:sub.o add.o mult.o div.o main.ogcc sub.o add.o div.o main.o 改成 srcsub.o add.o mult.o div.o main.otargetapp$(target):$(src)$(CC) $(src) -o $(target) 截图&#xff1a…

vue中使用Pinia和Vuex详解

最具有争议的Pinia和Vuex那个更好&#xff1f; 我们使用Vue2的时候&#xff0c;Vuex作为一个状态管理工具在组件中使用方便了很多。Vue3推出后&#xff0c;虽然相对于Vue2很多东西都变了&#xff0c;但是核心的东西还是没有变的&#xff0c;比如说状态管理、路由等等。实际上&a…

怎么修复vcruntime140_1.dll缺失,vcruntime140_1.dll丢失的解决方案

vcruntime140_1.dll是什么&#xff1f; vcruntime140_1.dll是Windows操作系统中的一个动态链接库文件&#xff0c;它属于Microsoft Visual C Redistributable的一部分。这个文件包含了一些在运行使用了C语言编写的程序时所需的函数和资源。当系统无法找到或加载vcruntime140_1…

视频号自曝核心算法,流量获取攻略解析

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 近日&#xff0c;视频号爆出了一项重要消息&#xff1a;微信视频号竟然自爆了自己的核心算法逻辑。 这在一般平台是比较罕见的。因为一旦自曝了算法&#xff0c;就会有人根据这个算法去作弊&…

【已解决】Flask项目报错TypeError: tuple indices must be integers or slices, not str

文章目录 问题情境报错及分析报错代码分析 解决方案必要的解决方法可能有用的解决方法 问题情境 本解决方案适用情境&#xff1a;在本地可以正常运行的flask项目&#xff0c;放到云服务器报错TypeError: tuple indices must be integers or slices, not str&#xff0c;即代码…

有哪些记事本app可以用来整理个人笔记?

我总是在思考一个问题&#xff0c;为什么现在越来越多的人选择使用记事本app&#xff1f;它们相比传统笔记本&#xff0c;又有什么吸引人的地方呢&#xff1f;这其实并不难理解。因为&#xff0c;记事本app不仅可以让我们及时记录重要信息&#xff0c;还能对这些信息进行系统、…

【C++技能树】String类解析与模拟实现

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我bua&#xff01; 终于放假啦&#xff0c;停更了一个月的博客也要重新拾起来了。近一个月都没怎么好好写代码&#xff0c;现在好多都看不懂了。在接下的时间里&#xff0…

京东内部 Spring Boot 全解笔记,精髓!

在使用传统的 Spring 去做 Java EE&#xff08;Java Enterprise Edition&#xff09;开发中&#xff0c;大量的 XML 文件存在于项目之中&#xff0c;导致 JavaEE 项目变得慢慢笨重起来&#xff0c;&#xff0c;繁琐的配置和整合第三方框架的配置&#xff0c;导致了开发和部署效…