惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
IT之家
IT之家
V
Visual Studio Blog
The Cloudflare Blog
博客园 - 司徒正美
Jina AI
Jina AI
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
S
SegmentFault 最新的问题
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
博客园 - Franky
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

博客园 - 何以解忧唯有撸码

Winform上位机也能写出媲美Avalonia的界面 C# 视频录制监控系统 玩转控件:封装个带图片的Label控件 c#实现包裹扣面单的几种方式 c#造个轮子-取色器TakeColor(附源码) OpenCvSharp基于颜色反差规避FBA面单贴标2 OpenCvSharp基于颜色反差规避FBA面单贴标 winform 绘制太阳,地球,月球 运作规律 玩转控件:GDI+动态绘制流程图 玩转控件:Fucking ERP之流程图 玩转控件:对Dev的GridControl控件扩展 玩转控件:封装Dev的SearchLookupEdit 玩转控件:对Dev中GridControl控件的封装和扩展 玩转控件:封装Dev的LabelControl和TextEdit 玩转控件:扩展Dev中SimpleButton 玩转控件:重写/重绘Dev中MessageBox弹窗控件 C# 视频监控系统(提供源码分享) C# 小游戏-拼图魔方【Game Puzzle】 玩转控件:重绘DEVEXPRESS中DateEdit控件 —— 让DateEdit支持只选择年月 (提供源码下载) 玩转控件:重绘ComboBox —— 让ComboBox多列显示 (提供源码下载) 分享:动态添加控件和公用章水印工具 提供源码下载
c#造个轮子--GIF录制工具
何以解忧唯有撸码 · 2025-10-07 · via 博客园 - 何以解忧唯有撸码

        在以往几篇文章里面,大家都可以看到各种录制的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