当前位置: 代码网 > it编程>编程语言>C# > C# WPF使用GDI实现截屏功能

C# WPF使用GDI实现截屏功能

2024年05月18日 C# 我要评论
前言wpf截屏时通常可以采用gdi+,调用起来比较方便。使用gdi也能实现截屏,截屏数据也能转成bitmapsource对象,当然调用流程会复杂一些,而且需要引入win32方法,唯一比较容易的就是可以

前言

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截屏的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com