namespace XFESystem.Desktop;
/// Native-pixel coverage for chrome. Edge samples blend with the actual scene beneath them.
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 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 PrimitiveCache = [];
private static int PrimitiveCacheBytes;
/// Subpixel round-capped stroke; geometry is rasterized in native pixels.
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);
}
/// Independent corner radii and alpha; no opaque approximation of the scene background.
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);
/// Perimeter coverage stays inside the same rounded shape, without expanding its damage bounds.
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);
}
/// Bottom-right quarter circle with round endpoints, used along a window's existing radius.
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);
}