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

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Desktop;

/// <summary>Native-pixel coverage for chrome. Edge samples blend with the actual scene beneath them.</summary>
public static class DesktopChromeRenderer
{
    private sealed record Corners(byte[][] Coverage);
    private sealed record Stroke(int Left, int Top, int Width, int Height, byte[] Coverage);
    private static readonly Dictionary<int, Corners> CornerCache = [];
    private static readonly Dictionary<(int X, int Y, int Scale), Stroke> StrokeCache = [];
    private enum PrimitiveKind { Capsule, Rounded, RoundedBorder, QuarterArc }
    private readonly record struct PrimitiveKey(PrimitiveKind Kind, int Width, int Height,
        int X1, int Y1, int X2, int Y2, int A, int B, int C, int D, int E, byte Opacity);
    private static readonly Dictionary<PrimitiveKey, byte[]> PrimitiveCache = [];
    private static int PrimitiveCacheBytes;

    /// <summary>Subpixel round-capped stroke; geometry is rasterized in native pixels.</summary>
    public static void ThickLine(IDesktopCanvas canvas, uint color, double x1, double y1, double x2, double y2,
        double thickness, byte opacity = 255)
    {
        if (opacity == 0 || thickness <= 0) return;
        if (canvas is not IDesktopCoverageCanvas)
        {
            canvas.Line(color, (int)Math.Round(x1), (int)Math.Round(y1), (int)Math.Round(x2), (int)Math.Round(y2));
            return;
        }
        double scale = Math.Max(1, canvas.ScalePercent) / 100d;
        x1 *= scale; y1 *= scale; x2 *= scale; y2 *= scale;
        double half = thickness * scale / 2;
        int left = checked((int)Math.Floor(Math.Min(x1, x2) - half - .5));
        int top = checked((int)Math.Floor(Math.Min(y1, y2) - half - .5));
        int right = checked((int)Math.Ceiling(Math.Max(x1, x2) + half + .5));
        int bottom = checked((int)Math.Ceiling(Math.Max(y1, y2) + half + .5));
        var key = new PrimitiveKey(PrimitiveKind.Capsule, right - left, bottom - top,
            Quantize(x1 - left), Quantize(y1 - top), Quantize(x2 - left), Quantize(y2 - top), Quantize(half), 0, 0, 0, 0, opacity);
        DrawPrimitive(canvas, color, left, top, key);
    }

    /// <summary>Independent corner radii and alpha; no opaque approximation of the scene background.</summary>
    public static void RoundedAlphaFill(IDesktopCanvas canvas, uint color, double x, double y, double width, double height,
        double topLeftRadius, double topRightRadius, double bottomRightRadius, double bottomLeftRadius, byte opacity = 255)
        => RoundedAlpha(canvas, color, x, y, width, height, topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, 0, opacity);

    /// <summary>Perimeter coverage stays inside the same rounded shape, without expanding its damage bounds.</summary>
    public static void RoundedAlphaBorder(IDesktopCanvas canvas, uint color, double x, double y, double width, double height,
        double topLeftRadius, double topRightRadius, double bottomRightRadius, double bottomLeftRadius, double thickness, byte opacity = 255)
    {
        if (thickness <= 0) return;
        RoundedAlpha(canvas, color, x, y, width, height, topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, thickness, opacity);
    }

    private static void RoundedAlpha(IDesktopCanvas canvas, uint color, double x, double y, double width, double height,
        double topLeftRadius, double topRightRadius, double bottomRightRadius, double bottomLeftRadius, double thickness, byte opacity)
    {
        if (opacity == 0 || width <= 0 || height <= 0) return;
        double maximumRadius = Math.Min(width, height);
        topLeftRadius = Math.Clamp(topLeftRadius, 0, maximumRadius); topRightRadius = Math.Clamp(topRightRadius, 0, maximumRadius);
        bottomRightRadius = Math.Clamp(bottomRightRadius, 0, maximumRadius); bottomLeftRadius = Math.Clamp(bottomLeftRadius, 0, maximumRadius);
        // CSS-style independent corners may use the full edge length when
        // the neighboring corner is square (the resize quarter-corner does).
        double factor = Math.Min(1, Math.Min(Math.Min(width / Math.Max(1, topLeftRadius + topRightRadius), width / Math.Max(1, bottomLeftRadius + bottomRightRadius)),
            Math.Min(height / Math.Max(1, topLeftRadius + bottomLeftRadius), height / Math.Max(1, topRightRadius + bottomRightRadius))));
        topLeftRadius *= factor; topRightRadius *= factor; bottomRightRadius *= factor; bottomLeftRadius *= factor;
        if (canvas is not IDesktopCoverageCanvas)
        {
            if (opacity == 255) RoundedFill(canvas, color, (int)Math.Round(x), (int)Math.Round(y),
                (int)Math.Round(width), (int)Math.Round(height), (int)Math.Round(maximumRadius));
            return; // Alpha cannot be approximated safely without access to the actual background.
        }
        double scale = Math.Max(1, canvas.ScalePercent) / 100d;
        double nativeX = x * scale, nativeY = y * scale;
        int left = checked((int)Math.Floor(nativeX)), top = checked((int)Math.Floor(nativeY));
        int right = checked((int)Math.Ceiling((x + width) * scale)), bottom = checked((int)Math.Ceiling((y + height) * scale));
        var key = new PrimitiveKey(thickness == 0 ? PrimitiveKind.Rounded : PrimitiveKind.RoundedBorder, right - left, bottom - top,
            Quantize(nativeX - left), Quantize(nativeY - top), Quantize(width * scale), Quantize(height * scale),
            Quantize(topLeftRadius * scale), Quantize(topRightRadius * scale), Quantize(bottomRightRadius * scale),
            Quantize(bottomLeftRadius * scale), Quantize(Math.Min(thickness, maximumRadius) * scale), opacity);
        DrawPrimitive(canvas, color, left, top, key);
    }

    /// <summary>Bottom-right quarter circle with round endpoints, used along a window's existing radius.</summary>
    public static void QuarterArc(IDesktopCanvas canvas, uint color, double centerX, double centerY, double radius,
        double thickness, byte opacity = 255)
    {
        if (opacity == 0 || radius <= 0 || thickness <= 0) return;
        if (canvas is not IDesktopCoverageCanvas)
        {
            int previousX = (int)Math.Round(centerX + radius), previousY = (int)Math.Round(centerY);
            for (int step = 1; step <= 12; step++)
            {
                double angle = step * Math.PI / 24;
                int nextX = (int)Math.Round(centerX + radius * Math.Cos(angle)), nextY = (int)Math.Round(centerY + radius * Math.Sin(angle));
                canvas.Line(color, previousX, previousY, nextX, nextY); previousX = nextX; previousY = nextY;
            }
            return;
        }
        double scale = Math.Max(1, canvas.ScalePercent) / 100d;
        centerX *= scale; centerY *= scale; radius *= scale;
        double half = thickness * scale / 2;
        int left = checked((int)Math.Floor(centerX - half - .5)), top = checked((int)Math.Floor(centerY - half - .5));
        int right = checked((int)Math.Ceiling(centerX + radius + half + .5)), bottom = checked((int)Math.Ceiling(centerY + radius + half + .5));
        var key = new PrimitiveKey(PrimitiveKind.QuarterArc, right - left, bottom - top,
            Quantize(centerX - left), Quantize(centerY - top), 0, 0, Quantize(radius), Quantize(half), 0, 0, 0, opacity);
        DrawPrimitive(canvas, color, left, top, key);
    }

    private static int Quantize(double value) => checked((int)Math.Round(value * 1024));
    private static void DrawPrimitive(IDesktopCanvas canvas, uint color, int left, int top, PrimitiveKey key)
    {
        if (key.Width <= 0 || key.Height <= 0) return;
        if ((long)key.Width * key.Height > 16 * 1024 * 1024) throw new ArgumentOutOfRangeException(nameof(key));
        byte[] mask;
        lock (PrimitiveCache)
        {
            if (!PrimitiveCache.TryGetValue(key, out mask!))
            {
                mask = RasterizePrimitive(key);
                if (mask.Length <= 64 * 1024 && PrimitiveCache.Count < 1024 && PrimitiveCacheBytes + mask.Length <= 8 * 1024 * 1024)
                {
                    PrimitiveCache.Add(key, mask);
                    PrimitiveCacheBytes += mask.Length;
                }
            }
        }
        ((IDesktopCoverageCanvas)canvas).BlendCoverage(color, left, top, key.Width, key.Height, mask);
    }

    private static byte[] RasterizePrimitive(PrimitiveKey key)
    {
        double x1 = key.X1 / 1024d, y1 = key.Y1 / 1024d, x2 = key.X2 / 1024d, y2 = key.Y2 / 1024d;
        double a = key.A / 1024d, b = key.B / 1024d, c = key.C / 1024d, d = key.D / 1024d;
        var mask = new byte[key.Width * key.Height];
        for (int row = 0; row < key.Height; row++)
            for (int col = 0; col < key.Width; col++)
            {
                double px = col + .5, py = row + .5, distance;
                if (key.Kind == PrimitiveKind.Capsule)
                {
                    double dx = x2 - x1, dy = y2 - y1, length = dx * dx + dy * dy;
                    double t = length == 0 ? 0 : Math.Clamp(((px - x1) * dx + (py - y1) * dy) / length, 0, 1);
                    dx = px - x1 - t * dx; dy = py - y1 - t * dy;
                    distance = Math.Sqrt(dx * dx + dy * dy) - a;
                }
                else if (key.Kind == PrimitiveKind.QuarterArc)
                {
                    double dx = px - x1, dy = py - y1;
                    distance = dx >= 0 && dy >= 0 ? Math.Abs(Math.Sqrt(dx * dx + dy * dy) - a) - b :
                        Math.Min(Math.Sqrt((dx - a) * (dx - a) + dy * dy), Math.Sqrt(dx * dx + (dy - a) * (dy - a))) - b;
                }
                else
                {
                    double x = px - x1, y = py - y1;
                    distance = RoundedDistance(x, y, x2, y2, a, b, c, d);
                    if (key.Kind == PrimitiveKind.RoundedBorder)
                    {
                        double inset = key.E / 1024d;
                        double inner = RoundedDistance(x - inset, y - inset, x2 - inset * 2, y2 - inset * 2,
                            Math.Max(0, a - inset), Math.Max(0, b - inset), Math.Max(0, c - inset), Math.Max(0, d - inset));
                        int border = Math.Max(0, Alpha(.5 - distance) - Alpha(.5 - inner));
                        mask[row * key.Width + col] = (byte)((border * key.Opacity + 127) / 255);
                        continue;
                    }
                }
                mask[row * key.Width + col] = (byte)((Alpha(.5 - distance) * key.Opacity + 127) / 255);
            }
        return mask;
    }

    private static double RoundedDistance(double x, double y, double width, double height, double a, double b, double c, double d)
    {
        double distance = Math.Max(Math.Max(-x, x - width), Math.Max(-y, y - height));
        double radius = 0, cx = 0, cy = 0;
        if (x < a && y < a) { radius = a; cx = a; cy = a; }
        else if (x > width - b && y < b) { radius = b; cx = width - b; cy = b; }
        else if (x > width - c && y > height - c) { radius = c; cx = width - c; cy = height - c; }
        else if (x < d && y > height - d) { radius = d; cx = d; cy = height - d; }
        return radius <= 0 ? distance : Math.Max(distance, Math.Sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)) - radius);
    }

    public static void RoundedFill(IDesktopCanvas canvas, uint color, int x, int y, int width, int height, int radius)
    {
        if (width <= 0 || height <= 0) return;
        radius = Math.Clamp(radius, 0, Math.Min(width, height) / 2);
        if (radius == 0) { canvas.Fill(color, x, y, width, height); return; }
        if (canvas is not IDesktopCoverageCanvas coverage)
        {
            // Recording/non-pixel adapters retain the same silhouette.
            canvas.Fill(color, x, y + radius, width, height - 2 * radius);
            for (int row = 0; row < radius; row++)
            {
                double dy = radius - row - .5;
                int inset = radius - (int)Math.Sqrt(radius * radius - dy * dy);
                canvas.Fill(color, x + inset, y + row, width - 2 * inset, 1);
                canvas.Fill(color, x + inset, y + height - row - 1, width - 2 * inset, 1);
            }
            return;
        }
        double scale = Math.Max(1, canvas.ScalePercent) / 100d;
        int edge = Pixel(radius, scale);
        if (edge <= 0) { canvas.Fill(color, x, y, width, height); return; }
        if (edge > 1024) throw new ArgumentOutOfRangeException(nameof(radius));
        Corners corners;
        lock (CornerCache)
        {
            if (!CornerCache.TryGetValue(edge, out corners!))
            {
                var samples = new byte[4][];
                for (int i = 0; i < samples.Length; i++) samples[i] = new byte[checked(edge * edge)];
                for (int row = 0; row < edge; row++)
                    for (int column = 0; column < edge; column++)
                    {
                        double dx = edge - column - .5, dy = edge - row - .5;
                        byte alpha = Alpha(edge + .5 - Math.Sqrt(dx * dx + dy * dy));
                        samples[0][row * edge + column] = alpha;
                        samples[1][row * edge + edge - 1 - column] = alpha;
                        samples[2][(edge - 1 - row) * edge + column] = alpha;
                        samples[3][(edge - 1 - row) * edge + edge - 1 - column] = alpha;
                    }
                corners = new(samples);
                if (edge <= 256 && CornerCache.Count < 32) CornerCache.Add(edge, corners);
            }
        }
        // Only the small four corner tiles need alpha blending. The interior
        // stays a clipped, fast fill, including during window dragging.
        canvas.Fill(color, x + radius, y, width - 2 * radius, height);
        canvas.Fill(color, x, y + radius, radius, height - 2 * radius);
        canvas.Fill(color, x + width - radius, y + radius, radius, height - 2 * radius);
        int left = Pixel(x, scale), top = Pixel(y, scale);
        int right = Pixel(x + width, scale) - edge, bottom = Pixel(y + height, scale) - edge;
        coverage.BlendCoverage(color, left, top, edge, edge, corners.Coverage[0]);
        coverage.BlendCoverage(color, right, top, edge, edge, corners.Coverage[1]);
        coverage.BlendCoverage(color, left, bottom, edge, edge, corners.Coverage[2]);
        coverage.BlendCoverage(color, right, bottom, edge, edge, corners.Coverage[3]);
    }

    public static void Line(IDesktopCanvas canvas, uint color, int x1, int y1, int x2, int y2)
    {
        if (canvas is not IDesktopCoverageCanvas coverage) { canvas.Line(color, x1, y1, x2, y2); return; }
        double scale = Math.Max(1, canvas.ScalePercent) / 100d;
        int originX = Pixel(x1, scale), originY = Pixel(y1, scale);
        int dx = Pixel(x2, scale) - originX, dy = Pixel(y2, scale) - originY;
        Stroke stroke;
        var key = (dx, dy, canvas.ScalePercent);
        lock (StrokeCache)
        {
            if (!StrokeCache.TryGetValue(key, out stroke!))
            {
                double half = scale / 2;
                int left = (int)Math.Floor(Math.Min(0, dx) - .5), top = (int)Math.Floor(Math.Min(0, dy) - .5);
                int right = (int)Math.Ceiling(Math.Max(0, dx) + scale + .5), bottom = (int)Math.Ceiling(Math.Max(0, dy) + scale + .5);
                int width = right - left, height = bottom - top;
                long size = (long)width * height;
                if (size > 16 * 1024 * 1024) throw new ArgumentOutOfRangeException(nameof(x2));
                var samples = new byte[(int)size];
                double lengthSquared = (double)dx * dx + (double)dy * dy;
                for (int row = 0; row < height; row++)
                    for (int col = 0; col < width; col++)
                    {
                        double px = col + left + .5 - half, py = row + top + .5 - half;
                        double t = lengthSquared == 0 ? 0 : Math.Clamp((px * dx + py * dy) / lengthSquared, 0, 1);
                        double sx = px - t * dx, sy = py - t * dy;
                        samples[row * width + col] = Alpha(half + .5 - Math.Sqrt(sx * sx + sy * sy));
                    }
                stroke = new(left, top, width, height, samples);
                if (size <= 64 * 1024 && StrokeCache.Count < 96) StrokeCache.Add(key, stroke);
            }
        }
        coverage.BlendCoverage(color, originX + stroke.Left, originY + stroke.Top, stroke.Width, stroke.Height, stroke.Coverage);
    }

    private static int Pixel(int value, double scale) => checked((int)Math.Round(value * scale, MidpointRounding.AwayFromZero));
    private static byte Alpha(double coverage) => (byte)Math.Clamp((int)Math.Round(coverage * 255), 0, 255);
}