如何用C#实现更快的游戏区域截图?
优化节奏游戏小区域截图性能的方案
一、修复BitBlt重复截图的问题
你遇到的BitBlt始终返回首次截图的情况,核心原因是游戏开启了硬件加速,导致窗口DC(设备上下文)无法实时同步最新画面。改用PrintWindow可以绕过这个缓存问题,同时保留BitBlt的高速特性:
[DllImport("user32.dll")] private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags); public Bitmap getBitmapAt(Point location, int width, int height) { // 先捕获整个窗口客户区 Bitmap fullWindowImg = new Bitmap(width, height, PixelFormat.Format32bppArgb); using (Graphics gdest = Graphics.FromImage(fullWindowImg)) { IntPtr hDC = gdest.GetHdc(); // PW_CLIENTONLY 只捕获窗口客户区,排除边框 PrintWindow(windowHandle, hDC, 0x00000001); gdest.ReleaseHdc(hDC); } // 裁剪到目标50x150区域 Rectangle cropRect = new Rectangle(location.X, location.Y, width, height); Bitmap croppedImg = fullWindowImg.Clone(cropRect, fullWindowImg.PixelFormat); fullWindowImg.Dispose(); return croppedImg; }
二、高性能方案:Windows Graphics Capture(Win10+)
如果你的程序运行在Win10 1903及以上版本,推荐使用Windows Graphics Capture API——这是微软专为低延迟屏幕捕获设计的硬件加速接口,捕获延迟通常在1-5ms,完全满足你8ms以内的要求:
using Windows.Graphics.Capture; using Windows.Graphics.DirectX; using Windows.Graphics.DirectX.Direct3D11; using System.Runtime.InteropServices; // 全局捕获资源(仅初始化一次) private GraphicsCaptureItem _captureItem; private Direct3D11CaptureFramePool _framePool; private GraphicsCaptureSession _session; private Bitmap _latestCroppedFrame; public void InitializeCapture(IntPtr windowHandle) { // 从游戏窗口句柄创建捕获项 _captureItem = GraphicsCaptureItem.CreateFromWindowId(new WindowId((ulong)windowHandle)); // 创建Direct3D设备和帧池 var d3dDevice = new Direct3D11Device(); _framePool = Direct3D11CaptureFramePool.Create( d3dDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, // 帧池大小,避免丢帧 _captureItem.Size); // 初始化捕获会话 _session = _framePool.CreateCaptureSession(_captureItem); _session.IsCursorCaptureEnabled = false; // 注册帧到达事件,实时获取最新画面 _framePool.FrameArrived += (sender, args) => { using (var frame = _framePool.TryGetNextFrame()) { var surface = frame.Surface; // 将Direct3D表面转换为Bitmap var fullBitmap = new Bitmap(surface.Description.Width, surface.Description.Height, PixelFormat.Format32bppArgb); var lockRect = new Rectangle(0, 0, fullBitmap.Width, fullBitmap.Height); var bits = fullBitmap.LockBits(lockRect, ImageLockMode.WriteOnly, fullBitmap.PixelFormat); surface.CopyToMemory(bits.Scan0, bits.Stride * fullBitmap.Height); fullBitmap.UnlockBits(bits); // 裁剪到目标50x150区域(替换成你的实际坐标) _latestCroppedFrame = fullBitmap.Clone(new Rectangle(100, 200, 50, 150), fullBitmap.PixelFormat); fullBitmap.Dispose(); } }; _session.StartCapture(); } // 获取最新截图 public Bitmap GetLatestCapture() { return _latestCroppedFrame?.Clone() as Bitmap; }
三、优化现有Graphics.CopyFromScreen的性能
如果暂时不想更换API,通过以下优化可将耗时压缩到10ms以内:
- 复用Bitmap对象,避免频繁创建/销毁的开销
- 固定捕获区域坐标,跳过重复调用
GetWindowRect - 使用
CaptureBlt替代SourceCopy,针对屏幕捕获优化
// 全局复用的Bitmap,仅在区域变化时重新创建 private static Bitmap _reusableBitmap; public static Bitmap CaptureImage(IntPtr handle, Rectangle targetCoords) { // 初始化或更新复用的Bitmap if (_reusableBitmap == null || _reusableBitmap.Width != targetCoords.Width || _reusableBitmap.Height != targetCoords.Height) { _reusableBitmap?.Dispose(); _reusableBitmap = new Bitmap(targetCoords.Width, targetCoords.Height, PixelFormat.Format32bppArgb); } using (Graphics g = Graphics.FromImage(_reusableBitmap)) { g.CopyFromScreen( new Point(targetCoords.Left, targetCoords.Top), Point.Empty, targetCoords.Size, CopyPixelOperation.CaptureBlt); } // 返回克隆对象,避免外部修改复用资源 return _reusableBitmap.Clone(new Rectangle(0,0,targetCoords.Width,targetCoords.Height), _reusableBitmap.PixelFormat); }
额外优化建议
- 直接读取Bitmap内存:如果检测逻辑只需要像素颜色值,可通过
LockBits直接访问内存,跳过格式转换 - 异步捕获:将截图操作放到后台线程,避免阻塞检测逻辑
- 关闭游戏VSync:若游戏开启垂直同步,画面更新频率会被限制在60Hz,可能影响截图实时性
内容的提问来源于stack exchange,提问作者Toll
相关产品推荐
相关产品推荐

