1. 项目背景与核心需求
去年开发一个教学演示系统时,需要将软件操作过程录制为动态图示。市面上GIF录制工具要么功能臃肿,要么存在水印,于是决定用C#自己造轮子。这个工具需要满足三个核心需求:
- 精准捕获屏幕指定区域
- 高效转换为GIF格式
- 保持较小文件体积
2. 技术方案选型
2.1 屏幕捕获方案对比
测试了三种主流方案:
- Graphics.CopyFromScreen:最基础API,但帧率不稳定
- DirectX截图:性能最好但依赖显卡驱动
- User32.dll的PrintWindow:兼容性最佳
最终选择方案3,实测在多数设备上能达到25FPS的稳定捕获。关键代码:
csharp复制[DllImport("user32.dll")]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
void CaptureWindow(IntPtr handle)
{
using (var bmp = new Bitmap(width, height))
{
using (var g = Graphics.FromImage(bmp))
{
IntPtr hdc = g.GetHdc();
PrintWindow(handle, hdc, 0);
g.ReleaseHdc(hdc);
}
// 处理捕获的位图...
}
}
2.2 GIF编码方案
比较了三种编码库:
- System.Drawing:内置但质量差
- FFmpeg:需要外部依赖
- NGif:纯C#实现
选用NGif的LZW编码器,通过调整调色板数量(限制在256色以内)平衡质量和体积。实测10秒动画可控制在2MB以内。
3. 核心实现细节
3.1 区域选择器实现
开发了类似QQ截图的交互控件:
csharp复制class RegionSelector : Control
{
// 鼠标拖动逻辑
protected override void OnMouseMove(MouseEventArgs e)
{
if (isDragging) {
selection.Width = e.X - selection.X;
selection.Height = e.Y - selection.Y;
Invalidate();
}
}
// 绘制半透明遮罩
protected override void OnPaint(PaintEventArgs e)
{
using (var brush = new SolidBrush(Color.FromArgb(128, 0, 0, 0)))
{
Region fullRegion = new Region(ClientRectangle);
fullRegion.Exclude(selection);
e.Graphics.FillRegion(brush, fullRegion);
}
}
}
3.2 帧率控制机制
采用生产者-消费者模式:
- 独立线程负责捕获(Producer)
- 内存队列缓冲图像帧
- 编码线程(Consumer)按目标FPS处理
关键配置参数:
csharp复制// 30FPS配置示例
int captureInterval = 33; // 毫秒
int maxQueueSize = 100; // 防内存溢出
4. 性能优化技巧
4.1 内存管理
发现三个内存黑洞:
- 未及时释放GDI对象 → 使用using严格管理
- 位图格式不统一 → 统一转换为Format32bppArgb
- 队列积压 → 动态调整捕获频率
4.2 编码加速
采用并行处理:
csharp复制Parallel.For(0, frameCount, i => {
var quantized = ColorQuantizer.Quantize(frames[i], 256);
gifEncoder.AddFrame(quantized);
});
5. 实际踩坑记录
-
多显示器问题:
- 解决方案:通过Screen.AllScreens获取所有屏幕信息
- 注意点:DPI缩放需要特殊处理
-
光标捕获异常:
- 需要额外调用:
csharp复制[DllImport("user32.dll")] static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); -
文件体积过大:
- 优化策略:
- 增量编码(仅存储变化像素)
- 调整帧间延迟
- 使用抖动算法
- 优化策略:
6. 完整调用示例
csharp复制var recorder = new GifRecorder();
recorder.SetRegion(new Rectangle(100, 100, 800, 600));
recorder.SetFps(15);
recorder.OnProgress += (p) => Console.WriteLine($"进度:{p}%");
await recorder.StartAsync("output.gif");
Console.WriteLine("录制完成");
关键提示:在Win10+系统需要设置应用清单要求:
xml复制<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
7. 扩展改进方向
- 智能区域跟踪:通过图像识别自动跟随窗口移动
- 后期编辑功能:添加标注/裁剪帧
- 云端同步:直接上传到图床服务
- 快捷键支持:全局热键控制录制
这个轮子后来成为团队内部标配工具,核心优势在于:
- 无第三方依赖(单个exe小于500KB)
- 可编程控制(支持API调用)
- 自定义程度高(可调节所有编码参数)