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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Desktop;
using XFESystem.Browser;
namespace XfePaintBaseline;
public static class BaselineBoxPainter
{
    public static void Paint(IDesktopCanvas canvas, BrowserDrawCommand command, BrowserRect bounds, BrowserRect visible)
    {
        if (command.Decoration is not { } box) return;
        if (canvas is not IDesktopCoverageCanvas coverage)
        { var area = box.Bounds.Translate(bounds.X-command.Bounds.X,bounds.Y-command.Bounds.Y).Intersect(visible); if(box.Background.Alpha>0)canvas.Fill(box.Background.Rgb,(int)area.X,(int)area.Y,(int)area.Width,(int)area.Height); return; }
        double scale = canvas.ScalePercent / 100d;
        BrowserRect actual = box.Bounds.Translate(bounds.X - command.Bounds.X, bounds.Y - command.Bounds.Y);
        var native = new BrowserRect(actual.X * scale, actual.Y * scale, actual.Width * scale, actual.Height * scale);
        var clip = new BrowserRect(visible.X * scale, visible.Y * scale, visible.Width * scale, visible.Height * scale);
        var radius = Scale(box.Radius, scale); var border = Scale(box.Border, scale);
        foreach (var shadow in box.Shadows.Reverse())
        {
            if (shadow.Inset) continue;
            var rect = new BrowserRect(native.X + (shadow.X - shadow.Spread) * scale, native.Y + (shadow.Y - shadow.Spread) * scale,
                Math.Max(0, native.Width + shadow.Spread * scale * 2), Math.Max(0, native.Height + shadow.Spread * scale * 2));
            double blur = shadow.Blur * scale, extent = blur * 1.5;
            var area = new BrowserRect(rect.X - extent - 1, rect.Y - extent - 1, rect.Width + extent * 2 + 2, rect.Height + extent * 2 + 2).Intersect(clip);
            DrawRows(coverage, area, shadow.Color.Rgb, (x, y) =>
            {
                double distance = Distance(rect, radius, x, y);
                return shadow.Color.Alpha * command.Opacity * (blur <= 0 ? Math.Clamp(.5 - distance, 0, 1) : .5 * (1 - Erf(distance / Math.Max(.5, blur / 2) / Math.Sqrt(2))));
            });
        }
        // Solid background and border are composited independently so transparent
        // borders retain the CSS background beneath them.
        if (box.Background.Alpha > 0) DrawRows(coverage, native.Intersect(clip), box.Background.Rgb,
            (x, y) => Coverage(native, radius, x, y) * box.Background.Alpha * command.Opacity);
        foreach (var gradient in box.Gradients.Reverse()) DrawGradientRows(coverage, native.Intersect(clip), native, radius, gradient, command.Opacity);
        var inner = new BrowserRect(native.X + border.Left, native.Y + border.Top, Math.Max(0, native.Width - border.Horizontal), Math.Max(0, native.Height - border.Vertical));
        var innerRadius = new BrowserEdges(Math.Max(0, radius.Top - Math.Max(border.Top, border.Left)), Math.Max(0, radius.Right - Math.Max(border.Top, border.Right)),
            Math.Max(0, radius.Bottom - Math.Max(border.Bottom, border.Right)), Math.Max(0, radius.Left - Math.Max(border.Bottom, border.Left)));
        if (border.Top + border.Right + border.Bottom + border.Left > 0)
            foreach (int side in new[] { 0, 1, 2, 3 })
            {
                CssColor color = side switch { 0 => box.Top, 1 => box.Right, 2 => box.Bottom, _ => box.Left };
                BrowserRect sideBounds = side switch
                {
                    0 => new(native.X, native.Y, native.Width, Math.Max(border.Top, Math.Max(radius.Top, radius.Right))),
                    1 => new(native.Right - Math.Max(border.Right, Math.Max(radius.Right, radius.Bottom)), native.Y, Math.Max(border.Right, Math.Max(radius.Right, radius.Bottom)), native.Height),
                    2 => new(native.X, native.Bottom - Math.Max(border.Bottom, Math.Max(radius.Left, radius.Bottom)), native.Width, Math.Max(border.Bottom, Math.Max(radius.Left, radius.Bottom))),
                    _ => new(native.X, native.Y, Math.Max(border.Left, Math.Max(radius.Top, radius.Left)), native.Height)
                };
                DrawRows(coverage, sideBounds.Intersect(clip), color.Rgb, (x, y) =>
                {
                    // Corner ownership follows the closest normalized border side.
                    double t = border.Top > 0 ? (y - native.Top) / border.Top : double.MaxValue;
                    double r = border.Right > 0 ? (native.Right - x) / border.Right : double.MaxValue;
                    double b = border.Bottom > 0 ? (native.Bottom - y) / border.Bottom : double.MaxValue;
                    double l = border.Left > 0 ? (x - native.Left) / border.Left : double.MaxValue;
                    double minimum = Math.Min(Math.Min(t, r), Math.Min(b, l));
                    int owner = t == minimum ? 0 : r == minimum ? 1 : b == minimum ? 2 : 3;
                    return owner != side ? 0 : Math.Max(0, Coverage(native, radius, x, y) - Coverage(inner, innerRadius, x, y)) * color.Alpha * command.Opacity;
                });
            }
    }
    private static BrowserEdges Scale(BrowserEdges value, double scale) => new(value.Top * scale, value.Right * scale, value.Bottom * scale, value.Left * scale);
    public static double Coverage(BrowserRect bounds, BrowserEdges radii, double x, double y) => Math.Clamp(.5 - Distance(bounds, radii, x, y), 0, 1);
    private static double Distance(BrowserRect rect, BrowserEdges radii, double x, double y)
    {
        if (rect.Width <= 0 || rect.Height <= 0) return double.MaxValue;
        double radius = x < rect.X + rect.Width / 2 ? y < rect.Y + rect.Height / 2 ? radii.Top : radii.Left : y < rect.Y + rect.Height / 2 ? radii.Right : radii.Bottom;
        double qx = Math.Abs(x - rect.X - rect.Width / 2) - rect.Width / 2 + radius;
        double qy = Math.Abs(y - rect.Y - rect.Height / 2) - rect.Height / 2 + radius;
        return Math.Sqrt(Math.Max(0, qx) * Math.Max(0, qx) + Math.Max(0, qy) * Math.Max(0, qy)) + Math.Min(Math.Max(qx, qy), 0) - radius;
    }
    private static double Erf(double x)
    {
        double sign = x < 0 ? -1 : 1; x = Math.Abs(x); double t = 1 / (1 + .3275911 * x);
        return sign * (1 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - .284496736) * t + .254829592) * t * Math.Exp(-x * x));
    }
    private static void DrawRows(IDesktopCoverageCanvas coverage, BrowserRect area, uint rgb, Func<double, double, double> sample)
    {
        int left = (int)Math.Floor(area.Left), right = (int)Math.Ceiling(area.Right), top = (int)Math.Floor(area.Top), bottom = (int)Math.Ceiling(area.Bottom);
        if (right <= left || bottom <= top) return;
        var row = new byte[right - left];
        for (int y = top; y < bottom; y++)
        {
            bool any = false;
            for (int x = left; x < right; x++) { row[x - left] = (byte)Math.Clamp(Math.Round(sample(x + .5, y + .5) * 255), 0, 255); any |= row[x - left] != 0; }
            if (any) coverage.BlendCoverage(rgb, left, y, row.Length, 1, row);
        }
    }
    private static void DrawGradientRows(IDesktopCoverageCanvas coverage, BrowserRect area, BrowserRect box, BrowserEdges radius, BrowserCssGradient gradient, double opacity)
    {
        int left=(int)Math.Floor(area.Left),right=(int)Math.Ceiling(area.Right),top=(int)Math.Floor(area.Top),bottom=(int)Math.Ceiling(area.Bottom);
        if(right<=left||bottom<=top)return;
        var row=new byte[right-left]; var colors=new uint[row.Length];
        for(int y=top;y<bottom;y++)
        {
            for(int x=left;x<right;x++)
            { var color=BaselineGradientSample(gradient,x+.5-box.X,y+.5-box.Y,box.Width,box.Height); colors[x-left]=color.Rgb; row[x-left]=(byte)Math.Clamp(Math.Round(color.Alpha*opacity*Coverage(box,radius,x+.5,y+.5)*255),0,255); }
            for(int start=0;start<row.Length;)
            { int end=start+1; while(end<row.Length&&colors[end]==colors[start])end++; coverage.BlendCoverage(colors[start],left+start,y,end-start,1,row.AsSpan(start,end-start));start=end; }
        }
    }
    private static CssColor BaselineGradientSample(BrowserCssGradient gradient, double x, double y, double width, double height)
    {
        bool Radial=gradient.Radial; double CenterX=gradient.CenterX,CenterY=gradient.CenterY,Angle=gradient.Angle;var Stops=gradient.Stops;
        double position;
        if (Radial)
        {
            double cx=CenterX*width,cy=CenterY*height,rx=Math.Max(Math.Abs(cx),Math.Abs(width-cx)),ry=Math.Max(Math.Abs(cy),Math.Abs(height-cy));
            double radius=Math.Sqrt(rx*rx+ry*ry); position=radius<=0?1:Math.Sqrt((x-cx)*(x-cx)+(y-cy)*(y-cy))/radius;
        }
        else
        {
            double radians=Angle*Math.PI/180,dx=Math.Sin(radians),dy=-Math.Cos(radians),length=Math.Abs(width*dx)+Math.Abs(height*dy);
            position=length<=0?1:.5+((x-width/2)*dx+(y-height/2)*dy)/length;
        }
        if (position<=Stops[0].Position) return Stops[0].Color;
        for(int i=1;i<Stops.Count;i++)
            if(position<=Stops[i].Position)
            { double length=Stops[i].Position-Stops[i-1].Position; return Mix(Stops[i-1].Color,Stops[i].Color,length<=0?0:1-(position-Stops[i-1].Position)/length); }
        return Stops[^1].Color;
    }
    private static CssColor Mix(CssColor a, CssColor b, double aWeight, double alphaMultiplier = 1)
    {
        double aa = a.Alpha * aWeight, ba = b.Alpha * (1 - aWeight), alpha = aa + ba;
        if (alpha <= 0) return new(0, 0);
        uint Channel(int shift) => (uint)Math.Clamp(Math.Round((((a.Rgb >> shift) & 255) * aa + ((b.Rgb >> shift) & 255) * ba) / alpha), 0, 255);
        return new(Channel(16) << 16 | Channel(8) << 8 | Channel(0), alpha * alphaMultiplier);
    }

}