namespace XFESystem.Browser; /// A bounded RGBA canvas backing store. Pixels use straight ARGB, initially transparent. public sealed class BrowserCanvasSurface { private uint[] pixels; public BrowserCanvasSurface(int width, int height) { Width = width; Height = height; pixels = new uint[checked(width * height)]; } public int Width { get; private set; } public int Height { get; private set; } public ReadOnlyMemory Pixels => pixels; public long Revision { get; private set; } internal BrowserCanvasSurface CopyForPaintSnapshot() { var copy = new BrowserCanvasSurface(Width, Height); pixels.AsSpan().CopyTo(copy.pixels); copy.Revision = Revision; return copy; } internal void Resize(int width, int height) { Width = width; Height = height; pixels = new uint[checked(width * height)]; ++Revision; } internal void Fill(double x, double y, double width, double height, uint argb, bool clear = false) { if (!double.IsFinite(x + y + width + height)) return; if (width < 0) { x += width; width = -width; } if (height < 0) { y += height; height = -height; } var left = (int)Math.Clamp(Math.Floor(x), 0, Width); var top = (int)Math.Clamp(Math.Floor(y), 0, Height); var right = (int)Math.Clamp(Math.Ceiling(x + width), 0, Width); var bottom = (int)Math.Clamp(Math.Ceiling(y + height), 0, Height); for (var row = top; row < bottom; ++row) for (var column = left; column < right; ++column) pixels[row * Width + column] = clear ? 0 : Composite(pixels[row * Width + column], argb); ++Revision; } internal uint Pixel(int x, int y) => x >= 0 && y >= 0 && x < Width && y < Height ? pixels[y * Width + x] : 0; internal void PutPixel(int x, int y, uint value) { if (x >= 0 && y >= 0 && x < Width && y < Height) pixels[y * Width + x] = value; } internal void Commit() => ++Revision; internal void BlendMask(ReadOnlySpan mask, int width, int height, double x, double y, double scaleX, double scaleY, uint color, double alpha) { if (width < 0 || height < 0 || mask.Length != checked(width * height)) throw new ArgumentException("Invalid canvas text mask."); for (var row = 0; row < height; ++row) for (var col = 0; col < width; ++col) { uint coverage = (uint)Math.Clamp(Math.Round(mask[row * width + col] * alpha), 0, 255); if (coverage == 0) continue; Fill(x + col * scaleX, y + row * scaleY, scaleX, scaleY, coverage << 24 | color); } ++Revision; } private static uint Composite(uint destination, uint source) { uint alpha = source >> 24; if (alpha == 255) return source; if (alpha == 0) return destination; uint destinationAlpha = destination >> 24; uint outputAlpha = alpha + (destinationAlpha * (255 - alpha) + 127) / 255; if (outputAlpha == 0) return 0; uint result = outputAlpha << 24; for (var shift = 0; shift < 24; shift += 8) { uint premultiplied = ((source >> shift) & 255) * alpha + (((destination >> shift) & 255) * destinationAlpha * (255 - alpha) + 127) / 255; result |= ((premultiplied + outputAlpha / 2) / outputAlpha) << shift; } return result; } }