namespace XFESystem.Browser;
/// <summary>A bounded RGBA canvas backing store. Pixels use straight ARGB, initially transparent.</summary>
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<uint> 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<byte> 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;
}
}
namespace XFESystem.Browser;
/// <summary>A bounded RGBA canvas backing store. Pixels use straight ARGB, initially transparent.</summary>
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<uint> 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<byte> 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;
}
}