Rasterizer for the project logo mark. It scans vector shapes per layer into an RGBA8888 pixel buffer, supports even-odd fills, cutouts, antialiasing via supersampled scanlines, optional rounded tile background and premultiplied alpha compositing.
using System;
using System.Collections.Generic;
/// <summary>
/// One layer of the logo: shapes filled even-odd in a colour, or a "cut" that punches through to the background.
/// </summary>
public readonly record struct LogoLayer( (byte R, byte G, byte B) Color, bool Cut, (float X, float Y)[][] Shapes );
/// <summary>
/// Paints the logo's layers into an RGBA image with smooth edges. Paint.DrawPolygon can't do even-odd fills
/// or cut-outs (the logo's </> is punched through the pepper), so the logo is rasterized here instead.
/// Each layer is a scanline pass over all its edges, composited in order.
/// </summary>
public static class MarkRasterizer
{
const int SubRows = 4;
/// <summary>
/// Renders layers (unit box, y down) as RGBA8888, placed at (offsetX, offsetY) and scaled by
/// <paramref name="scale"/> (pixels). With <paramref name="tileColor"/> it all sits on a rounded tile in that
/// colour (cuts show the tile); without, the background is transparent (cuts are see-through).
/// </summary>
public static byte[] Render( int size, IReadOnlyList<LogoLayer> layers, float offsetX, float offsetY, float scale,
(byte R, byte G, byte B)? tileColor, float cornerRadius )
{
var count = size * size;
// Premultiplied colour + alpha, so "over" and "cut" are simple blends
var r = new float[count];
var g = new float[count];
var b = new float[count];
var a = new float[count];
if ( tileColor is { } tile )
{
Array.Fill( r, tile.R / 255f );
Array.Fill( g, tile.G / 255f );
Array.Fill( b, tile.B / 255f );
Array.Fill( a, 1f );
}
foreach ( var layer in layers )
{
var coverage = Coverage( size, layer.Shapes, offsetX, offsetY, scale );
var (cr, cg, cb) = layer.Cut && tileColor is { } bg
? (bg.R / 255f, bg.G / 255f, bg.B / 255f)
: (layer.Color.R / 255f, layer.Color.G / 255f, layer.Color.B / 255f);
for ( int i = 0; i < count; i++ )
{
var c = Math.Clamp( coverage[i], 0, 1 );
if ( c <= 0 ) continue;
if ( layer.Cut && tileColor is null )
{
// Punch a hole: everything underneath fades out
r[i] *= 1 - c; g[i] *= 1 - c; b[i] *= 1 - c; a[i] *= 1 - c;
continue;
}
r[i] = r[i] * (1 - c) + cr * c;
g[i] = g[i] * (1 - c) + cg * c;
b[i] = b[i] * (1 - c) + cb * c;
a[i] = a[i] * (1 - c) + c;
}
}
var pixels = new byte[count * 4];
for ( int py = 0; py < size; py++ )
{
for ( int px = 0; px < size; px++ )
{
var i = py * size + px;
var alpha = a[i];
var o = i * 4;
if ( alpha > 0 )
{
pixels[o + 0] = ToByte( r[i] / alpha );
pixels[o + 1] = ToByte( g[i] / alpha );
pixels[o + 2] = ToByte( b[i] / alpha );
}
if ( tileColor is not null ) alpha *= RoundedRectCoverage( px + 0.5f, py + 0.5f, size, cornerRadius );
pixels[o + 3] = ToByte( alpha );
}
}
return pixels;
}
/// <summary>
/// Even-odd coverage of a set of shapes, per pixel. All edges go into one scanline pass, so overlapping
/// shapes cancel out, which is what lets the </> strokes cross cleanly.
/// </summary>
private static float[] Coverage( int size, (float X, float Y)[][] shapes, float offsetX, float offsetY, float scale )
{
var coverage = new float[size * size];
var crossings = new List<float>();
var edges = new List<(float X0, float Y0, float X1, float Y1)>();
foreach ( var shape in shapes )
{
for ( int i = 0; i < shape.Length; i++ )
{
var p = shape[i];
var q = shape[(i + 1) % shape.Length];
if ( p.Y == q.Y ) continue;
edges.Add( (offsetX + p.X * scale, offsetY + p.Y * scale, offsetX + q.X * scale, offsetY + q.Y * scale) );
}
}
for ( int py = 0; py < size; py++ )
{
for ( int sub = 0; sub < SubRows; sub++ )
{
var y = py + (sub + 0.5f) / SubRows;
crossings.Clear();
foreach ( var e in edges )
{
if ( (y >= e.Y0) == (y >= e.Y1) ) continue;
crossings.Add( e.X0 + (y - e.Y0) * (e.X1 - e.X0) / (e.Y1 - e.Y0) );
}
crossings.Sort();
for ( int k = 0; k + 1 < crossings.Count; k += 2 )
AddSpan( coverage, py * size, size, crossings[k], crossings[k + 1], 1f / SubRows );
}
}
return coverage;
}
/// <summary>
/// Adds a horizontal span's coverage to a row, with fractional coverage for the end pixels.
/// </summary>
private static void AddSpan( float[] coverage, int row, int width, float a, float b, float weight )
{
a = Math.Clamp( a, 0, width );
b = Math.Clamp( b, 0, width );
if ( b <= a ) return;
var ia = (int)a;
var ib = (int)b;
if ( ia == ib )
{
coverage[row + Math.Min( ia, width - 1 )] += (b - a) * weight;
return;
}
coverage[row + ia] += (ia + 1 - a) * weight;
for ( int i = ia + 1; i < ib; i++ ) coverage[row + i] += weight;
if ( ib < width ) coverage[row + ib] += (b - ib) * weight;
}
/// <summary>
/// How much of a pixel (by its centre) is inside a size x size square with rounded corners.
/// </summary>
private static float RoundedRectCoverage( float x, float y, int size, float radius )
{
var half = size / 2f;
var qx = MathF.Abs( x - half ) - (half - radius);
var qy = MathF.Abs( y - half ) - (half - radius);
var outside = MathF.Sqrt( MathF.Max( qx, 0 ) * MathF.Max( qx, 0 ) + MathF.Max( qy, 0 ) * MathF.Max( qy, 0 ) );
var distance = outside + MathF.Min( MathF.Max( qx, qy ), 0 ) - radius;
return Math.Clamp( 0.5f - distance, 0, 1 );
}
private static byte ToByte( float v ) => (byte)Math.Round( Math.Clamp( v, 0, 1 ) * 255 );
}