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