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
- Overview
- Color Alpha
- Transparent Windows
- TransparencyBrush
- Border Compositing
- Control-Level Alpha
- Performance
- Examples
- Best Practices
Overview
Alpha blending happens at two levels:
- Intra-window — controls with semi-transparent colors blend against the window's background within the
CharacterBufferat paint time. - 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:
- If the cell's background alpha is 255, write it directly (fast path)
- If alpha < 255, call
ResolveCellBelow()to find what's beneath at that screen position - Composite:
resolvedBg = Color.Blend(cellBg, bgBelow) - For empty cells, bubble up the character from below with faded foreground
- 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
ResolveCellBelowwalk. Cost scales with overlapping window count (typically 2-3) - Scratch buffer: allocated once per
Rendererinstance, 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
WithBackgroundColoralpha 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
- Compositor Effects — post-processing buffer effects
- Gradients — gradient backgrounds that work with transparency
- Desktop Background — the bottom layer of the compositing stack
- Rendering Pipeline — how the full render chain works