c#造个轮子--GIF录制工具

news/2025/10/7 18:42:12/文章来源:https://www.cnblogs.com/axing/p/19128750

        在以往几篇文章里面,大家都可以看到各种录制的GIF效果图,把gif放在文章开始,不仅可以减少很多冗余的解释白话文,更可以让读者一览无余看到文章大概要义。

以往都是使用“LicEcap”来录制的,那么我们是否能自己实现一个这样的工具呢?一方面国庆假期结束,练练代码手感,另一方面可以根据自己需求扩展需要的功能。

 

01介绍软件UI及操作

操作比较简单,以下是运行界面:

  1. 选择录制区域,绘制需要录制的ROI区域 

  2. 点击开始录制

  3. 录制结束后,停止录制即可.弹出保存路径保存gif

 

image.png

02效果图

  1. 整个运行作业图

    test.gif

  2. 实际录屏的ROI区域效果GIF

    eee.gif

03源码介绍

image

 

private void InitializeComponents()
{
    this.Text = "GIF录制工具";
    this.Size = new Size(400, 200);
    this.StartPosition = FormStartPosition.CenterScreen;

    // 选择区域按钮
    Button btnSelectArea = new Button();
    btnSelectArea.Text = "选择录制区域";
    btnSelectArea.Size = new Size(120, 30);
    btnSelectArea.Location = new Point(20, 20);
    btnSelectArea.Click += BtnSelectArea_Click;
    this.Controls.Add(btnSelectArea);

    // 开始录制按钮
    Button btnStart = new Button();
    btnStart.Text = "开始录制";
    btnStart.Size = new Size(120, 30);
    btnStart.Location = new Point(20, 60);
    btnStart.Click += BtnStart_Click;
    this.Controls.Add(btnStart);

    // 停止录制按钮
    Button btnStop = new Button();
    btnStop.Text = "停止录制";
    btnStop.Size = new Size(120, 30);
    btnStop.Location = new Point(20, 100);
    btnStop.Click += BtnStop_Click;
    this.Controls.Add(btnStop);

    // 帧率选择
    Label lblFrameRate = new Label();
    lblFrameRate.Text = "帧率:";
    lblFrameRate.Location = new Point(160, 65);
    lblFrameRate.Size = new Size(50, 20);
    this.Controls.Add(lblFrameRate);

    NumericUpDown numFrameRate = new NumericUpDown();
    numFrameRate.Value = frameRate;
    numFrameRate.Minimum = 1;
    numFrameRate.Maximum = 30;
    numFrameRate.Location = new Point(210, 65);
    numFrameRate.Size = new Size(60, 20);
    numFrameRate.ValueChanged += (s, e) => { frameRate = (int)numFrameRate.Value; };
    this.Controls.Add(numFrameRate);

    // 状态标签
    Label lblStatus = new Label();
    lblStatus.Text = "状态: 就绪";
    lblStatus.Location = new Point(160, 25);
    lblStatus.Size = new Size(200, 20);
    lblStatus.Name = "lblStatus";
    this.Controls.Add(lblStatus);

    // 录制计时器
    captureTimer = new System.Windows.Forms.Timer();
    captureTimer.Tick += CaptureTimer_Tick;
}

选择ROI录屏区域

private void StartAreaSelection()
{
    this.Hide();
    Thread.Sleep(500); // 等待窗体隐藏

    isSelectingArea = true;
    Cursor = Cursors.Cross;

    // 创建全屏透明窗体用于区域选择
    Form selectionForm = new Form();
    selectionForm.WindowState = FormWindowState.Maximized;
    selectionForm.FormBorderStyle = FormBorderStyle.None;
    selectionForm.BackColor = Color.Black;
    selectionForm.Opacity = 0.3;
    selectionForm.TopMost = true;
    selectionForm.Cursor = Cursors.Cross;

    Rectangle selectedArea = Rectangle.Empty;
    bool isDragging = false;
    Point dragStart = Point.Empty;

    selectionForm.MouseDown += (s, e) =>
    {
        if (e.Button == MouseButtons.Left)
        {
            isDragging = true;
            dragStart = e.Location;
        }
    };

    selectionForm.MouseMove += (s, e) =>
    {
        if (isDragging)
        {
            int x = Math.Min(dragStart.X, e.X);
            int y = Math.Min(dragStart.Y, e.Y);
            int width = Math.Abs(e.X - dragStart.X);
            int height = Math.Abs(e.Y - dragStart.Y);

            selectedArea = new Rectangle(x, y, width, height);
            selectionForm.Invalidate();
        }
    };

    selectionForm.MouseUp += (s, e) =>
    {
        if (e.Button == MouseButtons.Left && isDragging)
        {
            isDragging = false;
            if (selectedArea.Width > 10 && selectedArea.Height > 10)
            {
                recordingArea = selectedArea;
                UpdateStatus($"已选择区域: {recordingArea}");
            }
            selectionForm.Close();
        }
    };

    selectionForm.Paint += (s, e) =>
    {
        if (isDragging && !selectedArea.IsEmpty)
        {
            using (Pen pen = new Pen(Color.Red, 2))
            {
                e.Graphics.DrawRectangle(pen, selectedArea);
            }

            string sizeText = $"{selectedArea.Width} x {selectedArea.Height}";
            using (Font font = new Font("Arial", 12))
            using (Brush brush = new SolidBrush(Color.Red))
            {
                e.Graphics.DrawString(sizeText, font, brush, selectedArea.X, selectedArea.Y - 20);
            }
        }
    };

    selectionForm.KeyDown += (s, e) =>
    {
        if (e.KeyCode == Keys.Escape)
        {
            selectionForm.Close();
        }
    };

    selectionForm.FormClosed += (s, e) =>
    {
        isSelectingArea = false;
        Cursor = Cursors.Default;
        this.Show();
        this.BringToFront();
    };

    selectionForm.ShowDialog();
}

录制结束,保存GIF

private void SaveGif()
 {
     if (frames.Count == 0)
     {
         MessageBox.Show("没有可保存的帧!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
         return;
     }

     using (SaveFileDialog saveDialog = new SaveFileDialog())
     {
         saveDialog.Filter = "GIF 文件|*.gif";
         saveDialog.Title = "保存GIF文件";
         saveDialog.DefaultExt = "gif";

         if (saveDialog.ShowDialog() == DialogResult.OK)
         {
             try
             {
                 // 使用GifBitmapEncoder替代方案
                 SaveFramesAsGif(frames, saveDialog.FileName, frameRate);
                 MessageBox.Show($"GIF保存成功!\n文件: {saveDialog.FileName}\n帧数: {frames.Count}", "成功",
                     MessageBoxButtons.OK, MessageBoxIcon.Information);
             }
             catch (Exception ex)
             {
                 MessageBox.Show($"保存GIF时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
             }
         }
     }

     // 清理资源
     foreach (var frame in frames)
     {
         frame.Dispose();
     }
     frames.Clear();
 }
private void SaveFramesAsGif(List<Bitmap> frames, string filePath, int frameRate)
 {
     using (var collection = new MagickImageCollection())
     {
         foreach (var frame in frames)
         {
             using (var memoryStream = new MemoryStream())
             {
                 frame.Save(memoryStream, ImageFormat.Bmp);
                 memoryStream.Position = 0;

                 var image = new MagickImage(memoryStream);
                 image.AnimationDelay =Convert.ToUInt32( 100 / frameRate); // 设置帧延迟
                 collection.Add(image);
             }
         }

         // 优化GIF
         collection.Optimize();
         collection.Write(filePath);
     }
 }

主要用到第三方nuget包

  1. AnimatedGif

  2. Magick.NET-Q16-AnyCPU

结束语

  感谢各位耐心查阅!  如果您有更好的想法欢迎一起交流,有不懂的也可以微信公众号联系博主,作者公众号会经常发一些实用的小工具和demo源码,需要的可以去看看!另外,如果觉得本篇博文对您或者身边朋友有帮助的,麻烦点个关注!赠人玫瑰,手留余香,您的支持就是我写作最大的动力,感谢您的关注,期待和您一起探讨!再会!

image

 

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

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

相关文章

android binder(二)应用层编程实例 - 指南

android binder(二)应用层编程实例 - 指南2025-10-07 18:38 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: bl…

做网站 需要什么样的服务器西安房产网最新楼盘

本文整理自曹操出行实时计算负责人林震基于 HologresFlink 的曹操出行实时数仓建设的分享&#xff0c;内容主要分为以下六部分&#xff1a; 曹操出行业务背景介绍曹操出行业务痛点分析HologresFlink 构建企业级实时数仓曹操出行实时数仓实践曹操出行业务成果分析未来展望 一、曹…

高校学校网站建设广州番禺区属于什么风险地区

分词 分词是最基本的第一步。无论对于英文文本&#xff0c;还是中文文本都离不开分词。英文的分词相对比较简单&#xff0c;因为一般的英文写法里通过空格来隔开不同单词的。但对于中文&#xff0c;我们不得不采用一些算法去做分词。 常用的分词工具 # encodingutf-8 import …

网站建设完工报告那些网站建设的好

1.什么是设计模式 软件设计模式&#xff08;Design pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 …

netdata

https://blog.gitiu.com/posts/19026/ 默认用的db engine 并不是做持久化的, 持久化用外部的DB https://learn.netdata.cloud/docs/netdata-agent/resource-utilization/disk-&-retention

秦皇岛手机网站制作费用优化是什么

Mirrored String II 看到题解说是马拉车算法&#xff0c;我赛时并没想到&#xff08;好吧其实我是比赛完才知道有马拉车这个算法&#xff09; 因为字符串的长度只有1000&#xff0c;直接暴力跑其实就可以了&#xff0c;但是要注意的是&#xff1b;回文串有俩种形式&#xff0c…

arc3.2语言sort的时候报错:(sort < `(2 9 3 7 5 1)) 得写成此种:(sort > (pair (list 3 2)))

arc3.2语言sort的时候报错:(sort < `(2 9 3 7 5 1)) 得写成此种:(sort > (pair (list 3 2)))pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !i…

关于Elment-plus的el-table组件无法通过原生JS监听scroll事件

Element-ui的el-table组件能够通过原生JS监听scroll事件 Elment-plus的el-table组件无法通过原生JS监听scroll事件,貌似是由于虚拟滚动的原因?! 今天想给el-table进行无限滚动的时候才发现...

什么是网站可信认证网站内容不收录

C# WPF入门学习主线篇&#xff08;十八&#xff09;—— Border布局容器 欢迎来到C# WPF入门学习系列的第十八篇。在前几篇文章中&#xff0c;我们已经探讨了 Canvas、StackPanel、WrapPanel、DockPanel、Grid 和 UniformGrid 布局容器及其使用方法。本篇博客将介绍另一种非常…

噬菌体展示技术:从诺奖成果到疫苗研发,这一 “表型 - 基因型统一” 工具如何颠覆生物研究?

1985 年,George P. Smith 首次将外源基因插入丝状噬菌体 f1 的基因 Ⅲ,让目的多肽 “展示” 在噬菌体表面 —— 这一创举诞生了噬菌体展示技术,三十多年后,该技术因在抗体筛选、表位鉴定领域的突破性贡献,助力 Sm…

从零开始学Flink:实时流处理实战

本文以Apache Flink实时流处理为核心,通过SocketWordCount示例,系统讲解实时流处理基础概念、Flink优势、代码实现与并行处理机制,助力读者掌握Flink流处理实战技能。在大数据处理领域,实时流处理正变得越来越重要…

实用指南:解决 xmlsec.InternalError: (-1, ‘lxml xmlsec libxml2 library version mismatch‘)

实用指南:解决 xmlsec.InternalError: (-1, ‘lxml & xmlsec libxml2 library version mismatch‘)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: b…

高质量同人动画整理回顾记录的方式

爽了,每次清空网页、收藏夹释放大脑的时候都会感觉莫名的轻松,以前从来没有过这样的记录,用了几年才学来的经验啊;;; 超高质量原创动画制作,高质量同人动画太好看了,令人激动,某种内心被充盈了的感觉,只能体…

什么响应式网站莱芜新闻民生广角

一、问题描述 1、组态王【运行配置】界面没有【服务配置】的选项&#xff0c;无法将组态王Kingview配置为OPCUA服务器&#xff1b; 2、点击组态王【运行配置界面】的【服务配置】选项弹窗警告提示【试图执行的操作不受支持】&#xff0c;如下图所示&#xff1a; 二、问题分析 …

斑马打印机基础知识

斑马标签打印机分为热转印和热敏两种,其中,热转印需要碳带,热敏不需要。 热转印打印原理 打印头加热碳带上的油墨,将油墨熔化并转印到标签纸上。 热敏打印原理 打印头直接加热热敏纸,热量使涂层显现颜色成像。 热…

网站主要应用小程序开发工具代理平台

前言 2024.3.26是我在CSDN成为创作者的第128天&#xff0c;也是我第一次真正在网上创作的第128天 当我还在日常创作时&#xff0c;突然发现我收到了一封信 我想我可以分享一下这段时间的感想以及收获 机缘 在CSDN的这段时间里&#xff0c;我学习到了很多知识&#xff0c;也…

四川互联网广告人海淀区seo多少钱

System.out.print("今天开始继续读书摘录"); //不知道官方让不让我在博客里面记录 //如果不让的话我可能得转到别的上面记录 System.out.print("现在开始看《此生未完成》"); System.out.println("今天是第三天&#xff01;");有时候常常会想到那…

班级网站素材下载扬州哪家公司做网站比较好

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 火山引擎数智平台VeDI旗下的A/B测试平台&#xff08;DataTester&#xff09;&#xff0c;旨在为企业提供科学且可信的A/B测试能力及丰富的场景实验支持。随着企业的…

网站建设与维护经营范围网站 手机版网站开发合同

对比图&#xff08;截取部分&#xff09;&#xff1a; 注&#xff1a;先看分步&#xff0c;最后会附上完整代码&#xff08;如果有用&#xff0c;可以给小编点个赞吗&#xff1f;十分感谢&#xff09; 1.首先将前端返回相同的省份只展示一次 const obj {}; let keyList []r…