XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWorkflow

【WinUI】XFE基于windows的工作流

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using XFEWorkflow.Abstractions;

namespace XFEWorkflow.Plugins.Vision;

public sealed class ScreenFrame(Bitmap bitmap, ScreenRegion region, string coordinateSpace) : IDisposable
{
    public Bitmap Bitmap { get; } = bitmap;
    public ScreenRegion Region { get; } = region;
    public string CoordinateSpace { get; } = coordinateSpace;
    public void Dispose() => Bitmap.Dispose();
}

public static class ScreenCapture
{
    public static ScreenRegion DesktopBounds
    {
        get
        {
            using var dpi = new PhysicalPixelScope();
            return new ScreenRegion(GetSystemMetrics(76), GetSystemMetrics(77), GetSystemMetrics(78), GetSystemMetrics(79)).Validate();
        }
    }
    public static ScreenFrame Capture(ScreenRegion? requested = null)
    {
        using var dpi = new PhysicalPixelScope();
        var bounds = DesktopBounds;
        var region = requested ?? bounds;
        region.RequireInside(bounds);
        var bitmap = new Bitmap(region.Width, region.Height, PixelFormat.Format24bppRgb);
        try
        {
            using var graphics = Graphics.FromImage(bitmap);
            var desktop = GetDC(0);
            if (desktop == 0) throw new IOException("无法读取当前桌面。");
            try
            {
                var destination = graphics.GetHdc();
                try
                {
                    if (!BitBlt(destination, 0, 0, region.Width, region.Height, desktop, region.X, region.Y, 0x00CC0020u | 0x40000000u))
                        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "屏幕区域截图失败。");
                }
                finally { graphics.ReleaseHdc(destination); }
            }
            finally { ReleaseDC(0, desktop); }
            return new(bitmap, region, "screen");
        }
        catch { bitmap.Dispose(); throw; }
    }
    public static ScreenFrame Load(string path, ScreenRegion? requested = null)
    {
        if (!File.Exists(path)) throw new FileNotFoundException("找不到图像文件", path);
        using var original = Image.FromFile(Path.GetFullPath(path));
        var bounds = new ScreenRegion(0, 0, original.Width, original.Height).Validate();
        var region = requested ?? bounds;
        region.RequireInside(bounds);
        var bitmap = new Bitmap(region.Width, region.Height, PixelFormat.Format24bppRgb);
        try
        {
            using var graphics = Graphics.FromImage(bitmap);
            graphics.Clear(Color.White);
            graphics.DrawImage(original, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Rectangle(region.X, region.Y, region.Width, region.Height), GraphicsUnit.Pixel);
            return new(bitmap, region, "image");
        }
        catch { bitmap.Dispose(); throw; }
    }
    public static Color Pixel(int x, int y)
    {
        using var frame = Capture(new(x, y, 1, 1));
        return frame.Bitmap.GetPixel(0, 0);
    }
    // A Runner has no WinUI window and may inherit a different DPI context.
    // Keep the override synchronous and restore it on the same thread.
    private sealed class PhysicalPixelScope : IDisposable
    {
        private readonly nint previous = SetThreadDpiAwarenessContext(-4);
        public void Dispose() { if (previous != 0) SetThreadDpiAwarenessContext(previous); }
    }
    [DllImport("user32.dll")] private static extern int GetSystemMetrics(int index);
    [DllImport("user32.dll")] private static extern nint SetThreadDpiAwarenessContext(nint context);
    [DllImport("user32.dll")] private static extern nint GetDC(nint window);
    [DllImport("user32.dll")] private static extern int ReleaseDC(nint window, nint dc);
    [DllImport("gdi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool BitBlt(nint destination, int x, int y, int width, int height, nint source, int sourceX, int sourceY, uint operation);
}