Alpha Blending & Transparent Windows

SharpConsoleUI provides per-cell alpha compositing using Porter-Duff "over" blending. Any color with alpha < 255 composites against the content beneath it — including window backgrounds, control colors, and markup text.

Table of Contents#

  1. Overview
  2. Color Alpha
  3. Transparent Windows
  4. TransparencyBrush
  5. Border Compositing
  6. Control-Level Alpha
  7. Performance
  8. Examples
  9. Best Practices

Overview#

Alpha blending happens at two levels:

  1. Intra-window — controls with semi-transparent colors blend against the window's background within the CharacterBuffer at paint time.
  2. Inter-window — transparent windows (where BackgroundColor.A < 255) composite against windows below and the desktop background at render time.

The blending function is Color.Blend(src, dst) — a Porter-Duff "source over" operation that always produces a fully opaque result (A=255). Alpha is consumed at blend time.

Color Alpha#

Every Color has an alpha channel (0–255):

// Fully opaque (default)
var solid = new Color(255, 0, 0);           // A = 255

// Semi-transparent
var glass = new Color(255, 0, 0, 128);      // 50% red

// Fully transparent
var clear = Color.Transparent;               // A = 0

// Modify alpha on existing color
var faded = Color.Red.WithAlpha(128);        // 50% red

Porter-Duff Blend#

var result = Color.Blend(src, dst);
// src.A == 255 → returns src (fully opaque, covers everything)
// src.A == 0   → returns dst (fully transparent, shows what's below)
// src.A == 128 → blends 50/50 between src and dst RGB values
// Result always has A = 255

Transparent Windows#

Setting a window's background color with alpha < 255 makes it transparent:

var window = new WindowBuilder(ws)
    .WithTitle("Transparent")
    .WithSize(40, 15)
    .WithBackgroundColor(new Color(0, 20, 60, 180))  // 70% opaque dark blue
    .BuildAndShow();

Default Behavior (True Transparency)#

When no TransparencyBrush is set, the window is truly transparent:

  • Background: composites at the window's raw alpha against what's below
  • Character bubble-up: empty cells (spaces) in the window show characters from windows/desktop beneath, with faded foreground
  • Parabolic foreground fade: characters from below fade faster than the background bleed, using fadeAlpha = 1 - (1 - α/255)²
  • Block character guard: block characters (█▀▄░▒▓) use their foreground as the effective background for compositing, since they visually fill the entire cell
Window Alpha Background Opacity Foreground Fade Character Visibility
64 25% 44% Clearly visible
128 50% 75% Notably dimmed
180 71% 91% Barely visible
220 86% 98% Ghost

How It Works#

The compositor (Renderer.cs) walks the Z-order for each cell:

  1. If the cell's background alpha is 255, write it directly (fast path)
  2. If alpha < 255, call ResolveCellBelow() to find what's beneath at that screen position
  3. Composite: resolvedBg = Color.Blend(cellBg, bgBelow)
  4. For empty cells, bubble up the character from below with faded foreground
  5. Write the composited cell

ResolveCellBelow walks windows in descending Z-order, checking content areas and borders. It falls through to the desktop background buffer as the final layer.

Invalidation#

Transparent windows automatically re-render when:

  • The desktop background animates (gradient, pattern changes)
  • A window below the transparent window changes content
  • The transparent window itself is moved, resized, or activated

TransparencyBrush#

The TransparencyBrush overrides the default true-transparency compositing style. The brush does not own the color or alpha — it only controls how compositing is performed.

.WithBackgroundColor(new Color(0, 20, 60, 180))
.WithTransparencyBrush(TransparencyBrush.Mica())

Brush Styles#

Style Background Character Bubble-Up Use Case
(none) Raw alpha blend Yes, parabolic fg fade True transparency — see through the window
Acrylic Gaussian blend (fg+bg below) Yes, configurable power-curve fade Rich color impression with faded text bleed
Mica Gaussian blend (fg+bg below) No Tinted color field — no text visible, WinUI Mica analog
Tinted Raw bg-only blend No Simple colored overlay filter
Custom User-defined User-defined Full control via delegate

Acrylic#

Uses PerceivedCellColor to compute a Gaussian-style blend of the cell below's foreground and background, weighted by estimated glyph coverage. Characters bubble up with a configurable power-curve fade.

.WithTransparencyBrush(TransparencyBrush.Acrylic())

// Custom fade exponent (lower = more aggressive fade)
.WithTransparencyBrush(TransparencyBrush.Acrylic(fadeExponent: 0.25f))

// Custom text coverage estimate (0-255)
.WithTransparencyBrush(TransparencyBrush.Acrylic(textCoverage: 120))

Mica#

Like Acrylic for background (Gaussian color impression from content below) but without character bubble-up. You see tinted color fields, not text — similar to WinUI's Mica material.

.WithTransparencyBrush(TransparencyBrush.Mica())

Tinted#

Simple flat overlay — composites only background colors. No foreground influence from below, no character bubble-up, no block character guard. Just a colored filter.

.WithTransparencyBrush(TransparencyBrush.Tinted())

Custom#

Full control via a per-cell compositing delegate:

.WithTransparencyBrush(TransparencyBrush.WithCustom((topCell, cellBelow, alpha) =>
{
    var bg = Color.Blend(topCell.Background, cellBelow.Background);
    return new Cell(cellBelow.Character, cellBelow.Foreground, bg);
}))

The delegate receives (Cell topCell, Cell cellBelow, byte overlayAlpha) and returns the composited Cell.

Border Compositing#

Borders of transparent windows also composite against content below. The compositor re-renders border cells after BorderRenderer writes them, using the same ResolveCellBelow mechanism. Both horizontal borders (from cached border buffers) and vertical borders (including scrollbar) are composited.

Control-Level Alpha#

Controls inside a window can use semi-transparent colors:

// Semi-transparent markup text
window.AddControl(new MarkupControl(new List<string>
{
    "[#FF000080]50% red text[/]",           // foreground alpha
    "[on #0000FF80]50% blue background[/]"  // background alpha
}));

Control-level alpha blends within the window's CharacterBuffer at paint time. If the resulting cell background still has alpha < 255 (because the window background also has alpha), the compositor resolves it against layers below.

Terminal Transparency#

Modern terminal emulators (Kitty, Alacritty, WezTerm) support native background transparency via settings like background_opacity. To allow the terminal's transparency to show through SharpConsoleUI, set the desktop background to Color.Transparent:

Full Terminal Transparency#

// Set desktop to transparent — terminal wallpaper shows through exposed areas
windowSystem.DesktopBackgroundColor = Color.Transparent;

// Window with transparent background — terminal shows through
var window = new WindowBuilder(ws)
    .WithTitle("Transparent")
    .WithBackgroundColor(Color.Transparent)
    .Maximized()
    .Borderless()
    .BuildAndShow();

How It Works#

When a cell's background color has alpha = 0 (Color.Transparent), the ANSI formatter emits \x1b[49m (reset to terminal default background) instead of explicit \x1b[48;2;R;G;Bm. This tells the terminal emulator to use its configured default background, which may be semi-transparent.

Terminal Configuration#

Kitty (~/.config/kitty/kitty.conf):

background_opacity 0.7

Alacritty (~/.config/alacritty/alacritty.toml):

[window]
opacity = 0.7

WezTerm (~/.config/wezterm/wezterm.lua):

config.window_background_opacity = 0.7

Semi-Transparent Windows over Transparent Desktop#

When a semi-transparent window sits over a transparent desktop, there's a conflict: the window wants to apply a color tint, but there's no known color to tint against. The TerminalTransparencyMode option controls this:

// Default: window tint blends against black, emits explicit RGB.
// Terminal transparency is lost under the window, but the tint color is preserved.
var options = new ConsoleWindowSystemOptions(
    TerminalTransparencyMode: TerminalTransparencyMode.PreserveWindowColor
);

// Alternative: emit 49m for any non-opaque cell over transparent desktop.
// Terminal transparency shows through the window, but the tint color is lost.
var options = new ConsoleWindowSystemOptions(
    TerminalTransparencyMode: TerminalTransparencyMode.PreserveTerminalTransparency
);

Performance#

  • Opaque windows (A=255): zero overhead — single branch check, fast path only
  • Transparent windows: per-cell compositing with ResolveCellBelow walk. Cost scales with overlapping window count (typically 2-3)
  • Scratch buffer: allocated once per Renderer instance, reused across frames
  • Recursion: bounded by overlapping window count + depth guard at 20

Examples#

Semi-Transparent Overlay#

var overlay = new WindowBuilder(ws)
    .WithTitle("Overlay")
    .WithSize(50, 20)
    .WithBackgroundColor(new Color(0, 0, 40, 160))
    .BuildAndShow();

Frosted Glass Panel (Mica)#

var panel = new WindowBuilder(ws)
    .WithTitle("Settings")
    .WithSize(60, 25)
    .WithBackgroundColor(new Color(20, 20, 30, 200))
    .WithTransparencyBrush(TransparencyBrush.Mica())
    .BuildAndShow();

Dynamic Alpha with Slider#

byte alpha = 128;
slider.ValueChanged += (_, _) =>
{
    alpha = (byte)slider.Value;
    var old = window.BackgroundColor;
    window.BackgroundColor = new Color(old.R, old.G, old.B, alpha);
};

Best Practices#

  • Use transparent windows sparingly — each one adds per-cell compositing cost
  • The default true-transparency style is best for overlays where seeing content below matters
  • Use Mica or Tinted brush when you want visual depth without text bleed-through
  • Set WithBackgroundColor alpha between 160–220 for most uses — below 128 is very transparent, above 230 is barely noticeable
  • Transparent windows automatically invalidate when content below changes — no manual refresh needed

See Also#

Back to Main Documentation