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

XFEWorkflow

【WinUI】XFE基于windows的工作流

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

namespace XFEWorkflow.Plugins.Vision;

public sealed record TemplateImageData(string Value, int Width, int Height);

// PNG data stored in the node travels with copy/paste, undo and workflow export.
// Existing file paths remain valid for workflows and code written before 1.1.
public static class TemplateImage
{
    public const string Prefix = "data:image/png;base64,";
    public const int MaxBytes = 2 * 1024 * 1024, MaxPixels = 1_048_576;
    private static void ValidateSize(int width, int height)
    {
        if (width <= 0 || height <= 0 || width > 8192 || height > 8192 || (long)width * height > MaxPixels)
            throw new ArgumentException("模板最多 1048576 像素,单边最多 8192 像素;请框选目标控件附近的小区域。");
    }
    public static TemplateImageData Encode(Bitmap bitmap)
    {
        ValidateSize(bitmap.Width, bitmap.Height);
        using var data = new MemoryStream(); bitmap.Save(data, ImageFormat.Png);
        if (data.Length > MaxBytes) throw new ArgumentException("模板 PNG 超过 2 MiB,请缩小框选区域。");
        return new(Prefix + Convert.ToBase64String(data.GetBuffer(), 0, (int)data.Length), bitmap.Width, bitmap.Height);
    }
    public static TemplateImageData Crop(ScreenFrame frozen, ScreenRegion region)
    {
        region.RequireInside(frozen.Region); ValidateSize(region.Width, region.Height);
        using var bitmap = frozen.Bitmap.Clone(new Rectangle(region.X - frozen.Region.X, region.Y - frozen.Region.Y, region.Width, region.Height), PixelFormat.Format24bppRgb);
        return Encode(bitmap);
    }
    public static TemplateImageData Import(string path)
    {
        using var frame = ScreenCapture.Load(path); return Encode(frame.Bitmap);
    }
    public static ScreenFrame Load(string value)
    {
        if (!value.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) return ScreenCapture.Load(value);
        var bytes = Decode(value);
        using var data = new MemoryStream(bytes, false);
        using var original = Image.FromStream(data, false, true);
        var bitmap = new Bitmap(original.Width, original.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(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);
            return new(bitmap, new(0, 0, bitmap.Width, bitmap.Height), "image");
        }
        catch { bitmap.Dispose(); throw; }
    }
    public static byte[] Decode(string value)
    {
        if (!value.StartsWith(Prefix, StringComparison.Ordinal) || value.Length > Prefix.Length + (MaxBytes + 2) / 3 * 4)
            throw new ArgumentException("内嵌模板必须是最多 2 MiB 的 PNG 图像。");
        byte[] bytes;
        try { bytes = Convert.FromBase64String(value[Prefix.Length..]); }
        catch (FormatException ex) { throw new ArgumentException("模板图像数据损坏。", ex); }
        if (bytes.Length is < 33 or > MaxBytes || !bytes.AsSpan(0, 8).SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }) ||
            BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(8, 4)) != 13 || !bytes.AsSpan(12, 4).SequenceEqual("IHDR"u8))
            throw new ArgumentException("模板 PNG 头部损坏。");
        ValidateSize(BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(16, 4)), BinaryPrimitives.ReadInt32BigEndian(bytes.AsSpan(20, 4)));
        return bytes;
    }
}