前言
wpf截屏时通常可以采用gdi+,调用起来比较方便。使用gdi也能实现截屏,截屏数据也能转成bitmapsource对象,当然调用流程会复杂一些,而且需要引入win32方法,唯一比较容易的就是可以直接绘制异或鼠标。
一、导入gdi32
方法一、nuget获取
这种方法好处是简单方便,缺点是增加了依赖dll,生成的程序容量大一些且附带一些dll。
(1)、获取gdi32
(2)、获取user32
方法二、dllimport
使用dllimport将需要的win32 api导入。这样做工作量比较大,但是好处是无依赖,生成程序很小。
示例如下:
[dllimport(user32, setlasterror = false, exactspelling = true)] public static extern intptr getdc([in, optional] intptr ptr);
完整的gdi需要导入的所有接口见附录。
二、实现步骤
1、创建兼容dc
intptr srchdc = intptr.zero; intptr dsthdc = intptr.zero; srchdc = getdc(hwnd); dsthdc = createcompatibledc(srchdc);
2、创建位图
bitmapinfo bmi = new bitmapinfo(); intptr hbitmap = intptr.zero; intptr oldbitmap = intptr.zero; bmi.bmiheader.bisize = (uint)marshal.sizeof<bitmapinfoheader>(); bmi.bmiheader.biwidth = capwidth; bmi.bmiheader.biheight = -capheight; bmi.bmiheader.biplanes = 1; bmi.bmiheader.bibitcount = 24; bmi.bmiheader.bicompression = bitmapcompressionmode.bi_rgb; bmi.bmiheader.bisizeimage = 0; bmi.bmiheader.bixpelspermeter = 0; bmi.bmiheader.biypelspermeter = 0; bmi.bmiheader.biclrused = 0; bmi.bmiheader.biclrimportant = 0; hbitmap = createdibsection(dsthdc, in bmi, dibcolormode.dib_rgb_colors, out pvbits, intptr.zero, 0); oldbitmap = selectobject(dsthdc, hbitmap);
3、获取位图信息
需要获取位图的行对齐stride
bitmap bitmap; temp = marshal.allochglobal(marshal.sizeof<bitmap>()); if (getobject(hbitmap, marshal.sizeof<bitmap>(), temp) == 0) { throw new exception("getobject failed"); } bitmap = marshal.ptrtostructure<bitmap>(temp);
4、bitblt
bitblt(dsthdc, capx, capy, capwidth, capheight, srchdc, capx, capy, rasteroperationmode.srccopy | rasteroperationmode.captureblt)
5、获取数据
//行对齐 int stride=bitmap.bmwidthbytes; //宽 int width=bitmap.bmwidth; //高 int height=bitmap.bmheight; //位图数据 intptr pbuffer=bitmap.bmbits;
bitmap转成writeablebitmap(bitmapsource)
public static writeablebitmap towriteablebitmap(this bitmap bitmap) { var wb = new writeablebitmap(bitmap.bmwidth, bitmap.bmheight, 0, 0, bitmap.bmbitspixel == 32 ? pixelformats.bgra32 : pixelformats.bgr24, null); wb.writepixels(new int32rect(0, 0, bitmap.bmwidth, bitmap.bmheight), bitmap.bmbits, bitmap.bmheight * bitmap.bmwidthbytes, bitmap.bmwidthbytes, 0, 0); return wb; }
6、销毁资源
if (dsthdc != intptr.zero) { if (oldbitmap != intptr.zero) { var ret = selectobject(dsthdc, oldbitmap); if (ret == intptr.zero) { errors += "selectobject failed"; } } if (!deletedc(dsthdc)) { errors += "deletedc failed"; } } if (srchdc != intptr.zero) { if (!releasedc(hwnd, srchdc)) { errors += "releasedc failed"; } } if (!deleteobject(hbitmap)) { errors += "deleteobject failed"; } if (temp != intptr.zero) marshal.freehglobal(temp);
三、封装成对象
/************************************************************************ * @project: gdigrabber * @decription: gdi图片采集 * 可以根据提供的句柄采集图片或者获取快照。提供了采集区域提供了实际值和比例值 * 两种接口。采集提供了同步和异步两种方式,在主线程或者ui线程建议用异步,在非 * ui线程建议用同步。 * @verision: v1.0.0 * @author: xin nie * @create: 2024/03/13 23:57:00 * @lastupdate: 2024/03/13 23:57:00 ************************************************************************ * copyright @ 2024. all rights reserved. ************************************************************************/ public static class gdigrabber { /// <summary> /// 快照 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="hwnd"></param> /// <returns></returns> public static writeablebitmap? snapshot(int x, int y, int width, int height, nint hwnd = 0, bool ispaintmouse = true); /// <summary> /// 快照 /// 按比例,在任意分辨率,比如0,0,1,1就是全屏。 /// </summary> /// <param name="x">比例,0-1</param> /// <param name="y">比例,0-1</param> /// <param name="width">比例,0-1</param> /// <param name="height">比例,0-1</param> /// <param name="hwnd"></param> /// <returns></returns> public static writeablebitmap? snapshot(double x, double y, double width, double height, nint hwnd = 0, bool ispaintmouse = true); /// <summary> /// 采集,异步 /// 按比例,在任意分辨率,比如0,0,1,1就是全屏。 /// 用法 await foreach(var i in gdigrabbercapture){} /// 注意,在ui线程可以直接使用。 /// 在非ui线程需要确保dispatcher的运行,比如在线程最后调用dispatcher.run()、或 dispatcher.pushframe。 /// </summary> /// <param name="x">比例,0-1</param> /// <param name="y">比例,0-1</param> /// <param name="width">比例,0-1</param> /// <param name="height">比例,0-1</param> /// <param name="hwnd">句柄,为0则采集桌面</param> /// <param name="ispaintmouse">是否绘制鼠标</param> /// <returns>采集的数据对象</returns> public static iasyncenumerable<bitmap> capture(double x, double y, double width, double height, nint hwnd = 0, bool ispaintmouse = true); /// <summary> /// 采集,异步 /// 用法 await foreach(var i in gdigrabbercapture){} /// 注意,在ui线程可以直接使用。 /// 在非ui线程需要确保dispatcher的运行,比如在线程最后调用dispatcher.run()、或 dispatcher.pushframe。 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="hwnd">句柄,为0则采集桌面</param> /// <param name="ispaintmouse">是否绘制鼠标</param> /// <returns>采集的数据对象</returns> /// <exception cref="exception"></exception> public static async iasyncenumerable<bitmap> capture(int x, int y, int width, int height, nint hwnd = 0, bool ispaintmouse = true); public static /*ienumerable<bitmap>*/void capturesync(double x, double y, double width, double height, func<bitmap, bool> ongrab, nint hwnd = 0, bool ispaintmouse = true); /// <summary> /// 采集,同步 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="hwnd">句柄,为0则采集桌面</param> /// <param name="ispaintmouse">是否绘制鼠标</param> /// <param name="ongrab">采集回调,返回是否继续采集。之所以采用回调是因为,更好的设计应该是使用yeild return,但是会出现内存异常读写问题,暂时无法解决。 /// </param> /// <returns>采集的数据对象</returns> /// <exception cref="exception"></exception> public static /*ienumerable<bitmap>*/void capturesync(int x, int y, int width, int height, func<bitmap, bool> ongrab, nint hwnd = 0, bool ispaintmouse = false); /// <summary> /// 将bitmap转换成writeablebitmap /// 作者的设备测试此操作1080p耗时8ms /// </summary> /// <param name="bitmap">this</param> /// <returns>writeablebitmap</returns> public static writeablebitmap towirteablebitmap(this bitmap bitmap); /// <summary> /// 将bitmap数据拷贝到riteablebitmap /// 作者的设备测试此操作1080p耗时2ms /// </summary> /// <param name="bitmap">this</param> /// <param name="wb">writeablebitmap</param> public static void copytowriteablebitmap(this bitmap bitmap, writeablebitmap wb); }
四、完整代码
vs2022 .net6.0 wpf项目,采用dllimport的方式无任何依赖。
之后上传
五、使用示例
1、快照
(1)比例值区域截取
截取全屏(任意分辨率)
writeablebitmap? wb= gdigrabber.snapshot(0,0,1.0,1.0);
(2)实际值区域截取
writeablebitmap? wb= gdigrabber.snapshot(0,0,600,600);
(3)wpf中使用
wpfgdigrabber.xaml
<window x:class="wpfgdigrabber.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:wpfgdigrabber" mc:ignorable="d" title="mainwindow" height="450" width="800"> <grid > <image x:name="img"></image> </grid> </window>
wpfgdigrabber.cs
using ac; using system.windows; using system.windows.media.imaging; namespace wpfgdigrabber { /// <summary> /// interaction logic for mainwindow.xaml /// </summary> public partial class mainwindow : window { public mainwindow() { initializecomponent(); writeablebitmap? wb = gdigrabber.snapshot(0, 0, 1.0, 1.0); } } }
效果预览
2、采集
(1)、异步
ui线程使用
await foreach (var i in gdigrabber.capture(0, 0, 1.0, 1.0, 0)) { //img为image控件 if (img.source == null) img.source = i.towriteablebitmap(); else i.copytowriteablebitmapp(img.source as writeablebitmap); }
非ui线程使用,需要启动一个dispatcher用于调度消息以及阻塞线程避免结束。
new thread(async () => { bool isexit = false; var frame = new dispatcherframe(); var func = async () => { //循环采集 await foreach (var i in gdigrabber.capture(0, 0, 1.0, 1.0, 0)) { //dispatcher将操作切换到ui线程执行 dispatcher.invoke(() => { //writeablebitmap是和线程绑定的,需要在ui线程创建此对象。 writeablebitmap? wb = i.towriteablebitmap(); }); //退出采集 if (isexit) break; } //退出消息循环 frame.continue = false; }; func(); //启动dispatcher消息循环,此行阻塞 dispatcher.pushframe(frame); }) { isbackground = true }.start();
(2)、同步
同步的方式会阻塞,建议在非ui线程中使用。但要注意writeablebitmap 需要在ui线程中创建才能被控件使用。
gdigrabber.capturesync(0, 0, 1.0, 1.0, (bitmap) => { //获取到writeablebitmap对象 writeablebitmap wb = bitmap.towriteablebitmap(); //返回true为继续截屏,false为停止。 return true; });
(3)、wpf中使用(异步)
wpfgdigrabber.xaml
<window x:class="wpfgdigrabber.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:wpfgdigrabber" mc:ignorable="d" title="mainwindow" height="450" width="800"> <grid > <image x:name="img"></image> </grid> </window>
wpfgdigrabber.cs
using ac; using system.windows; using system.windows.media.imaging; namespace wpfgdigrabber { /// <summary> /// interaction logic for mainwindow.xaml /// </summary> public partial class mainwindow : window { public mainwindow() { initializecomponent(); capture(); } async void capture() { await foreach (var i in gdigrabber.capture(0, 0, 1.0, 1.0, 0)) { if (img.source == null) img.source = i.towriteablebitmap(); else i.copytowriteablebitmap(img.source as writeablebitmap); } } } }
效果预览
总结
以上就是今天要讲的内容,本文实现了的gdi截屏与gdi+对比性能略差一些,但也是可以一定程度满足使用,比如用来截屏或者制作放大镜。而且有一个好处是可以做到无额外依赖。总的来说,可以作为一种备选方案或者测试方案。
附录
dllimport
[structlayout(layoutkind.sequential)] public struct bitmap { public int bmtype; public int bmwidth; public int bmheight; public int bmwidthbytes; public ushort bmplanes; public ushort bmbitspixel; public intptr bmbits; } class winapiimport { const string gdi32 = "gdi32.dll"; const string user32 = "user32.dll"; [structlayout(layoutkind.sequential), serializable] public struct point { public int x; public int y; } [structlayout(layoutkind.sequential), serializable] public struct rect { public int left; public int top; public int right; public int bottom; } [structlayout(layoutkind.sequential, size = 4)] public struct rgbquad { public byte rgbblue; public byte rgbgreen; public byte rgbred; public byte rgbreserved; } [structlayout(layoutkind.sequential)] public struct bitmapinfo { public bitmapinfoheader bmiheader; [marshalas(unmanagedtype.byvalarray, sizeconst = 1)] public rgbquad[] bmicolors; } public enum bitmapcompressionmode : uint { bi_rgb = 0, bi_rle8 = 1, bi_rle4 = 2, bi_bitfields = 3, bi_jpeg = 4, bi_png = 5 } [structlayout(layoutkind.sequential, pack = 2)] public struct bitmapinfoheader { public uint bisize; public int biwidth; public int biheight; public ushort biplanes; public ushort bibitcount; public bitmapcompressionmode bicompression; public uint bisizeimage; public int bixpelspermeter; public int biypelspermeter; public uint biclrused; public uint biclrimportant; } public enum dibcolormode : int { dib_rgb_colors = 0, dib_pal_colors = 1 } public enum cursorstate { cursor_hidden = 0, cursor_showing = 0x00000001, cursor_suppressed = 0x00000002, } [structlayout(layoutkind.sequential, charset = charset.auto)] public struct cursorinfo { public uint cbsize; public cursorstate flags; public intptr hcursor; public point ptscreenpos; } [structlayout(layoutkind.sequential)] public sealed class iconinfo { public bool ficon; public int xhotspot; public int yhotspot; public intptr hbmmask; public intptr hbmcolor; } public enum rasteroperationmode { srccopy = 0x00cc0020, srcpaint = 0x00ee0086, srcand = 0x008800c6, srcinvert = 0x00660046, srcerase = 0x00440328, notsrccopy = 0x00330008, notsrcerase = 0x001100a6, mergecopy = 0x00c000ca, mergepaint = 0x00bb0226, patcopy = 0x00f00021, patpaint = 0x00fb0a09, patinvert = 0x005a0049, dstinvert = 0x00550009, blackness = 0x00000042, whiteness = 0x00ff0062, nomirrorbitmap = -2147483648, captureblt = 0x40000000 } [dllimport(user32, charset = charset.auto, exactspelling = true, setlasterror = true)] [return: marshalas(unmanagedtype.bool)] [system.security.securitycritical] public static extern bool getclientrect(intptr hwnd, out rect lprect); [dllimport(user32, setlasterror = false, exactspelling = true)] public static extern intptr getdesktopwindow(); [dllimport(user32, setlasterror = false, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool clienttoscreen(intptr hwnd, ref point lppoint); [dllimport(user32, setlasterror = false, exactspelling = true)] public static extern intptr getdc([in, optional] intptr ptr); [dllimport(gdi32, exactspelling = true, setlasterror = true)] public static extern intptr createcompatibledc([optional] intptr hdc); [dllimport(gdi32, setlasterror = false, exactspelling = true)] public static extern intptr createdibsection([in, optional] intptr hdc, in bitmapinfo pbmi, dibcolormode usage, out intptr ppvbits, [in, optional] intptr hsection, [in, optional] uint offset); [dllimport(gdi32, setlasterror = false, charset = charset.auto)] public static extern int getobject(intptr hgdiobj, int cbbuffer, intptr lpvobject); [dllimport(gdi32, exactspelling = true, setlasterror = true)] public static extern intptr selectobject(intptr hdc, intptr hobject); [dllimport(gdi32, exactspelling = true, setlasterror = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool bitblt(intptr hdc, int nxdest, int nydest, int nwidth, int nheight, intptr hdcsrc, int nxsrc, int nysrc, rasteroperationmode dwrop); [dllimport(gdi32, exactspelling = true, setlasterror = true)] [return: marshalas(unmanagedtype.bool)] [system.security.securitycritical] public static extern bool deletedc(intptr hdc); [dllimport(user32, setlasterror = false, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool releasedc(intptr hwnd, intptr hdc); [dllimport(gdi32, exactspelling = true, setlasterror = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool deleteobject(intptr hobject); [dllimport(user32, setlasterror = true, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool getcursorinfo(ref cursorinfo pci); [dllimport(user32, setlasterror = true, exactspelling = true)] public static extern intptr copyicon(intptr hicon); [dllimport(user32, setlasterror = true, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool geticoninfo(intptr hicon, [in, out] iconinfo piconinfo); [dllimport(user32, setlasterror = true, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool drawicon(intptr hdc, int x, int y, intptr hicon); [dllimport(user32, setlasterror = true, exactspelling = true)] [return: marshalas(unmanagedtype.bool)] public static extern bool destroycursor(intptr hcursor); [dllimport(user32, setlasterror = true, charset = charset.auto)] public static extern intptr loadcursor(intptr hinstance, string lpcursorname); }
以上就是c# wpf使用gdi实现截屏功能的详细内容,更多关于c# wpf截屏的资料请关注代码网其它相关文章!
发表评论