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

XFESystem

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

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

namespace XFESystem.Browser;

/// <summary>
/// A bounded, transparent command tile. Compose source pixels once, then inverse
/// sample them into the destination so downscaling does not repeatedly blend
/// adjacent source rows over one another. No framebuffer readback is required.
/// </summary>
internal sealed class BrowserAffineRasterCanvas : IDesktopCoverageCanvas, IDesktopRenderYieldCanvas
{
    private readonly IDesktopCanvas target;
    private readonly BrowserCanvasSurface surface;
    private readonly BrowserAffineTransform inverse;
    private readonly BrowserRect destination;
    private readonly int left, top;
    public BrowserRect SourceClip { get; }
    public BrowserAffineRasterCanvas(IDesktopCanvas target, BrowserRect bounds, BrowserRect clip, BrowserAffineTransform matrix)
    {
        this.target = target;
        if (!matrix.TryInverse(out inverse)) throw new ArgumentException("A singular transform cannot be painted.");
        double scale = ScalePercent / 100d;
        SourceClip = inverse.Bounds(clip).Intersect(bounds);
        left = (int)Math.Floor(SourceClip.Left * scale) - 1; top = (int)Math.Floor(SourceClip.Top * scale) - 1;
        int width = Math.Max(0, (int)Math.Ceiling(SourceClip.Right * scale) - left + 1);
        int height = Math.Max(0, (int)Math.Ceiling(SourceClip.Bottom * scale) - top + 1);
        if ((long)width * height > 16_777_216) throw new BrowserLimitException("The transformed paint tile exceeds its pixel limit.");
        surface = new(width, height);
        destination = matrix.Bounds(SourceClip).Intersect(clip).Intersect(new(0, 0, Width, Height));
    }
    public int Width => target.Width;
    public int Height => target.Height;
    public int NativeWidth => target.NativeWidth;
    public int NativeHeight => target.NativeHeight;
    public int ScalePercent => target.ScalePercent;
    public bool RenderYieldRequested => target is IDesktopRenderYieldCanvas yielding && yielding.RenderYieldRequested;
    public void BlendCoverage(uint rgb, int x, int y, int width, int height, ReadOnlySpan<byte> coverage)
    {
        if (width < 0 || height < 0 || (long)width * height != coverage.Length) throw new ArgumentException("Invalid transformed coverage dimensions.");
        int x0 = (int)Math.Clamp((long)left - x, 0, width), x1 = (int)Math.Clamp((long)left + surface.Width - x, 0, width);
        int y0 = (int)Math.Clamp((long)top - y, 0, height), y1 = (int)Math.Clamp((long)top + surface.Height - y, 0, height);
        for (int row = y0; row < y1; row++)
        {
            if (RenderYieldRequested) throw new DesktopRenderYieldException();
            for (int column = x0; column < x1; column++)
                surface.BlendPixel(x + column - left, y + row - top, (uint)coverage[row * width + column] << 24 | rgb);
        }
    }
    public void Fill(uint rgb, int x, int y, int width, int height)
    {
        double scale = ScalePercent / 100d;
        int x0 = (int)Math.Round(x * scale), x1 = (int)Math.Round((x + (double)width) * scale);
        int y0 = (int)Math.Round(y * scale), y1 = (int)Math.Round((y + (double)height) * scale);
        surface.Fill(x0 - left, y0 - top, x1 - x0, y1 - y0, 0xFF000000 | rgb);
    }
    public void Text(string text, uint rgb, int x, int y) => throw new NotSupportedException("Transformed text requires glyph coverage.");
    public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new NotSupportedException("Transformed lines require coverage.");
    public void Present() { }
    public void Flush()
    {
        double scale = ScalePercent / 100d;
        int x0 = Math.Max(0, (int)Math.Floor(destination.Left * scale)), x1 = Math.Min(NativeWidth, (int)Math.Ceiling(destination.Right * scale));
        int y0 = Math.Max(0, (int)Math.Floor(destination.Top * scale)), y1 = Math.Min(NativeHeight, (int)Math.Ceiling(destination.Bottom * scale));
        if (x1 <= x0 || y1 <= y0) return;
        var alpha = new byte[x1 - x0]; var colors = new uint[alpha.Length];
        for (int y = y0; y < y1; y++)
        {
            if (RenderYieldRequested) throw new DesktopRenderYieldException();
            for (int x = x0; x < x1; x++)
            {
                double a = 0, r = 0, g = 0, b = 0;
                // Four destination subpixels retain thin rotated/downscaled edges.
                for (int sample = 0; sample < 4; sample++)
                {
                    double ox = (sample & 1) == 0 ? .25 : .75, oy = sample < 2 ? .25 : .75;
                    var source = inverse.Point((x + ox) / scale, (y + oy) / scale);
                    double sx = source.X * scale - left - .5, sy = source.Y * scale - top - .5;
                    int ix = (int)Math.Floor(sx), iy = (int)Math.Floor(sy);
                    for (int row = 0; row < 2; row++) for (int column = 0; column < 2; column++)
                    {
                        uint pixel = surface.Pixel(ix + column, iy + row);
                        double weight = (column == 0 ? 1 - (sx - ix) : sx - ix) * (row == 0 ? 1 - (sy - iy) : sy - iy) / 4;
                        double coverage = (pixel >> 24) * weight;
                        a += coverage; r += ((pixel >> 16) & 255) * coverage; g += ((pixel >> 8) & 255) * coverage; b += (pixel & 255) * coverage;
                    }
                }
                alpha[x - x0] = (byte)Math.Clamp(Math.Round(a), 0, 255);
                colors[x - x0] = a <= 0 ? 0 : (uint)Math.Clamp(Math.Round(r / a), 0, 255) << 16 | (uint)Math.Clamp(Math.Round(g / a), 0, 255) << 8 | (uint)Math.Clamp(Math.Round(b / a), 0, 255);
            }
            for (int start = 0; start < alpha.Length;)
            {
                int end = start + 1; while (end < alpha.Length && colors[end] == colors[start]) end++;
                if (target is IDesktopCoverageCanvas coverage) coverage.BlendCoverage(colors[start], x0 + start, y, end - start, 1, alpha.AsSpan(start, end - start));
                else if (alpha[start] > 0) target.Fill(colors[start], (int)((x0 + start) / scale), (int)(y / scale), Math.Max(1, (int)((end - start) / scale)), Math.Max(1, (int)(1 / scale)));
                start = end;
            }
        }
    }
}