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 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 (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); } }