当前位置: 首页 > news >正文

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

        在以往几篇文章里面,大家都可以看到各种录制的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.hskmm.com/?act=detail&tid=26235

相关文章:

  • netdata
  • 关于Elment-plus的el-table组件无法通过原生JS监听scroll事件
  • arc3.2语言sort的时候报错:(sort < `(2 9 3 7 5 1)) 得写成此种:(sort > (pair (list 3 2)))
  • 噬菌体展示技术:从诺奖成果到疫苗研发,这一 “表型 - 基因型统一” 工具如何颠覆生物研究?
  • 从零开始学Flink:实时流处理实战
  • 高质量同人动画整理回顾记录的方式
  • 斑马打印机基础知识
  • 加拿大加密货币牌照:合规化加速数字资产成功
  • 深入解析:实时通信RTC与传统直播的异同
  • Exp2-后门原理与实践
  • 【Hexo】4.Hexo 博客文章进行加密 - 实践
  • 思考的动力
  • Software Foundations Vol.I : 多态与高阶函数(Poly)
  • 数学之美感悟。
  • 基于DeploySharp 的深度学习模型部署测试平台:支持YOLO全系列模型
  • 复制别人的vmware虚拟机无法联网ubuntu2204
  • 计算机网络学习分享-0
  • 预科02git使用
  • 预科01Python复习
  • 预科01Python学习
  • 5G-A:开启通信与行业变革的新时代 - 指南
  • Linq的join
  • 实用指南:用PyTorch从零开始编写DeepSeek-V2
  • 十所高校角逐对话式AI任务机器人挑战赛
  • SCIM漏洞挖掘实战指南
  • 虚拟文件系统
  • 博客迁移至CSDN!!!
  • 博客迁移到CSDN!!!
  • 手动实现一个C++绑定Lua脚本的库
  • 代码随想录算法训练营第十天 | leetcode 232 225 20 1047