C#抓图服务

本文基于C#使用BitBlt进行窗口抓图C#使用PrintWindow进行窗口抓图,和C++抓图服务对应。

C#抓图服务首先抽象出抓图接口,然后对接口做基于公共操作的抽象类封装,之后针对不同的抓图方式做差异化处理,最后根据接口实现抓图服务。

注意:Win32封装实现参考C#使用BitBlt进行窗口抓图

Github示例工程:SimpleWindowCapture

1、抓图接口

using System;
using Win32Proxy;

namespace CaptureProxy
{
    internal interface ICaptureHelper
    {
        bool Init(string windowName);
        bool Init(IntPtr handle);
        void Cleanup();
        bool RefreshWindow();
        bool ChangeWindowHandle(string windowName);
        bool ChangeWindowHandle(IntPtr handle);
        IntPtr Capture();
        bool Capture(out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect rect);

        Win32Types.Rect WindowRect { get; }
        Win32Types.Rect ClientRect { get; }
        int BitmapDataSize { get; }
        IntPtr BitmapPtr { get; }
        Win32Types.BitmapInfo BitmapInfo { get; }
    }
}
using System.ComponentModel;

namespace CaptureProxy
{
    public enum CaptureType
    {
        [Description("使用CreateDIBSection抓图,速度快,但是无法抓取D3D等渲染的窗口")]
        CreateDibSection = 0,

        [Description("使用PrintWindow抓图,速度慢(16ms左右),但是可以抓取D3D等渲染的窗口")]
        PrintWindow
    }
}

2、抓图抽象类

using System;
using Win32Proxy;

namespace CaptureProxy
{
    internal abstract class AbsCaptureHelper : ICaptureHelper
    {
        public Win32Types.Rect WindowRect => _windowRect;
        public Win32Types.Rect ClientRect => WinClientRect;
        public int BitmapDataSize => _bmpDataSize;
        public IntPtr BitmapPtr => HBitmap;
        public Win32Types.BitmapInfo BitmapInfo { get; } = new Win32Types.BitmapInfo();

        protected IntPtr HWnd = IntPtr.Zero;
        protected IntPtr HScrDc = IntPtr.Zero;
        protected IntPtr HMemDc = IntPtr.Zero;
        protected IntPtr HBitmap = IntPtr.Zero;
        protected IntPtr HOldBitmap = IntPtr.Zero;

        private Win32Types.Rect _windowRect;
        protected Win32Types.Rect WinClientRect;
        private int _bmpDataSize;

        protected abstract bool CommonInit();
        protected abstract IntPtr DoCapture();
        protected abstract bool DoCapture(out IntPtr bitsPtr);

        public bool Init(string windowName)
        {
            var handle = Win32Funcs.FindWindowWrapper(null, windowName);
            if (handle.Equals(IntPtr.Zero))
            {
                return false;
            }

            return Init(handle);
        }

        public bool Init(IntPtr handle)
        {
            HWnd = handle;

            //获取窗口大小
            if (!Win32Funcs.GetWindowRectWrapper(HWnd, out _windowRect)
                || !Win32Funcs.GetClientRectWrapper(HWnd, out WinClientRect))
            {
                return false;
            }

            _bmpDataSize = WinClientRect.Width * WinClientRect.Height * 3;

            return CommonInit();
        }

        public void Cleanup()
        {
            if (HBitmap.Equals(IntPtr.Zero))
            {
                return;
            }

            //删除用过的对象
            Win32Funcs.SelectObjectWrapper(HMemDc, HOldBitmap);
            Win32Funcs.DeleteObjectWrapper(HBitmap);
            Win32Funcs.DeleteDcWrapper(HMemDc);
            Win32Funcs.ReleaseDcWrapper(HWnd, HScrDc);

            HWnd = IntPtr.Zero;
            HScrDc = IntPtr.Zero;
            HMemDc = IntPtr.Zero;
            HBitmap = IntPtr.Zero;
            HOldBitmap = IntPtr.Zero;
            //_bitsPtr = IntPtr.Zero;
        }

        public bool RefreshWindow()
        {
            return ChangeWindowHandle(HWnd);
        }

        public bool ChangeWindowHandle(string windowName)
        {
            Cleanup();
            return Init(windowName);
        }

        public bool ChangeWindowHandle(IntPtr handle)
        {
            Cleanup();
            return Init(handle);
        }

        public IntPtr Capture()
        {
            if (HBitmap.Equals(IntPtr.Zero) || HMemDc.Equals(IntPtr.Zero) || HScrDc.Equals(IntPtr.Zero))
            {
                return IntPtr.Zero;
            }

            return DoCapture();
        }

        public bool Capture(out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect rect)
        {
            bitsPtr = IntPtr.Zero;
            bufferSize = _bmpDataSize;
            rect = WinClientRect;

            if (HBitmap.Equals(IntPtr.Zero) || HMemDc.Equals(IntPtr.Zero) || HScrDc.Equals(IntPtr.Zero))
            {
                return false;
            }

            return DoCapture(out bitsPtr);
        }
    }
}

3、抓图类实现

using System;
using Win32Proxy;

namespace CaptureProxy
{
    internal class DibCaptureHelper : AbsCaptureHelper
    {
        private Win32Types.BitmapInfo _bitmapInfo;
        private IntPtr _bitsPtr = IntPtr.Zero;

        protected override bool CommonInit()
        {
            //位图信息
            _bitmapInfo = new Win32Types.BitmapInfo {bmiHeader = new Win32Types.BitmapInfoHeader()};
            _bitmapInfo.bmiHeader.Init();
            _bitmapInfo.bmiHeader.biWidth = WinClientRect.Width;
            _bitmapInfo.bmiHeader.biHeight = WinClientRect.Height;
            _bitmapInfo.bmiHeader.biPlanes = 1;
            _bitmapInfo.bmiHeader.biBitCount = 24;
            _bitmapInfo.bmiHeader.biSizeImage = (uint) (WinClientRect.Width * WinClientRect.Height);
            _bitmapInfo.bmiHeader.biCompression = (uint) Win32Consts.BitmapCompressionMode.BI_RGB;

            HScrDc = Win32Funcs.GetWindowDcWrapper(HWnd);
            HMemDc = Win32Funcs.CreateCompatibleDcWrapper(HScrDc);
            HBitmap = Win32Funcs.CreateDibSectionWrapper(HMemDc, ref _bitmapInfo,
                (uint) Win32Consts.DibColorMode.DIB_RGB_COLORS,
                out _bitsPtr, IntPtr.Zero, 0);
            HOldBitmap = Win32Funcs.SelectObjectWrapper(HMemDc, HBitmap);

            return true;
        }

        protected override IntPtr DoCapture()
        {
            var ret = Win32Funcs.BitBltWrapper(
                HMemDc, 0, 0, WinClientRect.Width, WinClientRect.Height,
                HScrDc, 0, 0,
                (uint) Win32Consts.RasterOperationMode.SRCCOPY);
            return ret ? _bitsPtr : IntPtr.Zero;
        }

        protected override bool DoCapture(out IntPtr bitsPtr)
        {
            bitsPtr = _bitsPtr;
            var ret = Win32Funcs.BitBltWrapper(
                HMemDc, 0, 0, WinClientRect.Width, WinClientRect.Height,
                HScrDc, 0, 0,
                (uint) Win32Consts.RasterOperationMode.SRCCOPY);
            return ret;
        }
    }
}
using System;
using Win32Proxy;

namespace CaptureProxy
{
    internal class PrintCaptureHelper : AbsCaptureHelper
    {
        protected override bool CommonInit()
        {
            HScrDc = Win32Funcs.GetWindowDcWrapper(HWnd);
            HBitmap = Win32Funcs.CreateCompatibleBitmapWrapper(HScrDc, WinClientRect.Width, WinClientRect.Height);
            HMemDc = Win32Funcs.CreateCompatibleDcWrapper(HScrDc);
            HOldBitmap = Win32Funcs.SelectObjectWrapper(HMemDc, HBitmap);
            return true;
        }

        protected override IntPtr DoCapture()
        {
            var ret = Win32Funcs.PrintWindowWrapper(HWnd, HMemDc,
                (uint) Win32Consts.PrintWindowMode.PW_CLIENTONLY |
                (uint) Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT);
            return ret ? HBitmap : IntPtr.Zero;
        }

        protected override bool DoCapture(out IntPtr bitsPtr)
        {
            bitsPtr = HBitmap;
            var ret = Win32Funcs.PrintWindowWrapper(HWnd, HMemDc,
                (uint) Win32Consts.PrintWindowMode.PW_CLIENTONLY |
                (uint) Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT);
            return ret;
        }
    }
}

4、抓图服务

using System;
using System.Collections.Generic;
using Win32Proxy;

namespace CaptureProxy
{
    public class CaptureService
    {
        private readonly Dictionary<string, ICaptureHelper> _dicCaptureHelper;

        /// <summary>
        /// 注册抓图服务
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="windowName">窗口名称</param>
        /// <param name="type">抓图类型</param>
        /// <returns>true成功,false失败</returns>
        public bool RegisterCapture(string name, string windowName, CaptureType type = CaptureType.CreateDibSection)
        {
            if (string.IsNullOrEmpty(name) || _dicCaptureHelper.ContainsKey(name))
            {
                return false;
            }

            ICaptureHelper helper;
            switch (type)
            {
                case CaptureType.CreateDibSection:
                    helper = new DibCaptureHelper();
                    break;
                case CaptureType.PrintWindow:
                    helper = new PrintCaptureHelper();
                    break;
                default:
                    return false;
            }

            if (!helper.Init(windowName))
            {
                return false;
            }

            _dicCaptureHelper.Add(name, helper);

            return true;
        }

        /// <summary>
        /// 注册抓图服务
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="handle">窗口句柄</param>
        /// <param name="type">抓图类型</param>
        /// <returns>true成功,false失败</returns>
        public bool RegisterCapture(string name, IntPtr handle, CaptureType type = CaptureType.CreateDibSection)
        {
            if (string.IsNullOrEmpty(name) || _dicCaptureHelper.ContainsKey(name))
            {
                return false;
            }

            ICaptureHelper helper;
            switch (type)
            {
                case CaptureType.CreateDibSection:
                    helper = new DibCaptureHelper();
                    break;
                case CaptureType.PrintWindow:
                    helper = new PrintCaptureHelper();
                    break;
                default:
                    return false;
            }

            if (!helper.Init(handle))
            {
                return false;
            }

            _dicCaptureHelper.Add(name, helper);

            return true;
        }

        /// <summary>
        /// 获取是否已注册抓图服务
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <returns>true已注册,false未注册</returns>
        public bool IsRegister(string name)
        {
            return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
        }

        /// <summary>
        /// 注销抓图服务
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        public void UnRegisterCapture(string name)
        {
            if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
            {
                return;
            }

            _dicCaptureHelper[name]?.Cleanup();
            _dicCaptureHelper.Remove(name);
        }

        /// <summary>
        /// 清理所有抓图服务
        /// </summary>
        public void Cleanup()
        {
            foreach (var helper in _dicCaptureHelper)
            {
                helper.Value?.Cleanup();
            }

            _dicCaptureHelper.Clear();
        }

        public bool RefreshWindow(string name)
        {
            var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
            if (ret)
            {
                ret = _dicCaptureHelper[name].RefreshWindow();
            }

            return ret;
        }

        /// <summary>
        /// 修改窗口句柄
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="handle">窗口句柄</param>
        public bool ChangeWindowHandle(string name, IntPtr handle)
        {
            return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name) &&
                   _dicCaptureHelper[name].ChangeWindowHandle(handle);
        }

        /// <summary>
        /// 修改窗口句柄
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="windowName">窗口名称</param>
        public bool ChangeWindowHandle(string name, string windowName)
        {
            return !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name) &&
                   _dicCaptureHelper[name].ChangeWindowHandle(windowName);
        }

        /// <summary>
        /// 获取窗口尺寸
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="winRect">输出的窗口尺寸</param>
        /// <returns>true成功,false失败</returns>
        public bool GetWindowRect(string name, out Win32Types.Rect winRect)
        {
            var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
            winRect = ret ? _dicCaptureHelper[name].WindowRect : new Win32Types.Rect();
            return ret;
        }

        /// <summary>
        /// 获取窗口客户区尺寸
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="clientRect">输出的窗口客户区尺寸</param>
        /// <returns>true成功,false失败</returns>
        public bool GetClientRect(string name, out Win32Types.Rect clientRect)
        {
            var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
            clientRect = ret ? _dicCaptureHelper[name].ClientRect : new Win32Types.Rect();
            return ret;
        }

        /// <summary>
        /// 获取抓图数据大小
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="bmpDataSize">抓图数据大小</param>
        /// <returns>true成功,false失败</returns>
        public bool GetBitmapDataSize(string name, out int bmpDataSize)
        {
            var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
            bmpDataSize = ret ? _dicCaptureHelper[name].BitmapDataSize : 0;
            return ret;
        }

        public IntPtr GetBitmapPtr(string name)
        {
            if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
            {
                return IntPtr.Zero;
            }

            return _dicCaptureHelper[name].BitmapPtr;
        }

        public Win32Types.BitmapInfo GetBitmapInfo(string name)
        {
            if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
            {
                return new Win32Types.BitmapInfo();
            }

            return _dicCaptureHelper[name].BitmapInfo;
        }

        /// <summary>
        /// 获取抓图
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="bitsPtr">位图指针</param>
        /// <returns>true成功,false失败</returns>
        public bool Capture(string name, out IntPtr bitsPtr)
        {
            var ret = !string.IsNullOrEmpty(name) && _dicCaptureHelper.ContainsKey(name);
            bitsPtr = ret ? _dicCaptureHelper[name].Capture() : IntPtr.Zero;
            return ret && !bitsPtr.Equals(IntPtr.Zero);
        }

        /// <summary>
        /// 获取抓图
        /// </summary>
        /// <param name="name">抓图服务名称</param>
        /// <param name="bitsPtr">位图指针</param>
        /// <param name="bufferSize">位图数据大小</param>
        /// <param name="texSize">位图尺寸</param>
        /// <returns>true成功,false失败</returns>
        public bool Capture(string name, out IntPtr bitsPtr, out int bufferSize, out Win32Types.Rect texSize)
        {
            if (string.IsNullOrEmpty(name) || !_dicCaptureHelper.ContainsKey(name))
            {
                bitsPtr = IntPtr.Zero;
                bufferSize = 0;
                texSize = new Win32Types.Rect();
                return false;
            }

            return _dicCaptureHelper[name].Capture(out bitsPtr, out bufferSize, out texSize);
        }

        #region 单例模式

        private static CaptureService _instance;

        private static readonly object LockHelper = new object();

        private CaptureService()
        {
            _dicCaptureHelper = new Dictionary<string, ICaptureHelper>();
        }

        public static CaptureService Instance
        {
            get
            {
                if (_instance != null)
                {
                    return _instance;
                }

                lock (LockHelper)
                {
                    _instance = _instance ?? new CaptureService();
                }

                return _instance;
            }
        }

        #endregion
    }
}

5、使用示例

using System;
using System.Threading;
using CaptureProxy;
using Win32Proxy;

namespace SimpleWindowCapture
{
    internal sealed class CaptureHelper
    {
        public event Action<string, IntPtr, Win32Types.BitmapInfo> CaptureDone =
            (captureName, bitmapPtr, bitmapInfo) => { };

        public int Fps { get; set; } = 15;

        private double TimerInterval => 1000.0 / Fps;
        private string _captureName;
        private Timer _timer;

        public bool Start(string captureName, IntPtr handle, CaptureType type = CaptureType.CreateDibSection)
        {
            if (!CaptureService.Instance.RegisterCapture(captureName, handle, type))
            {
                return false;
            }

            _captureName = captureName;

            //创建守护定时器,马上执行
            _timer = new Timer(CaptureFunc, null,
                TimeSpan.FromMilliseconds(0), Timeout.InfiniteTimeSpan);

            return true;
        }

        public void Stop()
        {
            //移除定时器
            _timer?.Dispose();
            _timer = null;

            CaptureService.Instance.UnRegisterCapture(_captureName);
            _captureName = string.Empty;
        }

        private void CaptureFunc(object state)
        {
            Capture();

            //执行下次定时器
            _timer?.Change(TimeSpan.FromMilliseconds(TimerInterval), Timeout.InfiniteTimeSpan);
        }

        private void Capture()
        {
            IntPtr bitsPtr;
            if (!CaptureService.Instance.Capture(_captureName, out bitsPtr))
            {
                return;
            }

            var bitmapPtr = CaptureService.Instance.GetBitmapPtr(_captureName);
            var bitmapInfo = CaptureService.Instance.GetBitmapInfo(_captureName);
            CaptureDone.Invoke(_captureName, bitmapPtr, bitmapInfo);
        }
    }
}
posted @ 2020-04-30 17:18  xhubobo  阅读(970)  评论(0编辑  收藏  举报