Selkie--Alpha
NAME
Selkie::Alpha - The four alpha modes a cell's foreground or background can carry
SYNOPSIS
use Selkie::Alpha;
use Selkie::Style;
# A style that lets whatever is underneath show through, mixed 50/50
# with this style's own colours.
my $scrim = Selkie::Style.new(
fg => 0x000000,
bg => 0x000000,
fg-alpha => AlphaBlend,
bg-alpha => AlphaBlend,
);
# A style that contributes no background at all β the plane beneath
# paints the cell's background unchanged.
my $ghost = Selkie::Style.new(fg => 0xC0C0C0, bg-alpha => AlphaTransparent);
# The raw notcurses constant, if you're calling the bindings yourself.
alpha-constant(AlphaBlend); # NCALPHA_BLENDDESCRIPTION
Alpha in notcurses is a four-value enum, not a number. There is no 0.35 opacity. Each of a cell's two channels (foreground and background) carries two bits, and those two bits select one of exactly four behaviours:
AlphaOpaqueβ the default. This cell's colour is the colour. Nothing below shows through.AlphaBlendβ mix this cell's colour 50/50 with whatever the compositor has accumulated underneath it.AlphaTransparentβ contribute no colour at all; the accumulated colour below passes through untouched.AlphaHighContrastβ foreground only. Notcurses picks a foreground colour that contrasts with the resolved background, ignoring the style's ownfg.
Every widget starts fully opaque, so if you never touch these you get exactly the rendering Selkie has always produced.
One blend layer over opaque is an exact 50/50 mix
AlphaBlend is not "50% opacity" in the sense a compositing graphics API would mean it. It is a single averaging step, applied once per plane as notcurses walks the pile from top to bottom. Put one AlphaBlend plane over an opaque plane and the result is the exact arithmetic mean of the two colours, per channel:
# Black scrim (0x000000, AlphaBlend) over a 0x808080 background
# resolves to 0x404040 β (0x00 + 0x80) / 2 per component.The arithmetic is integer and truncating, so an odd sum rounds down: white (0xFFFFFF) under the same black scrim comes out 0x7F7F7F, not 0x808080. Both channels blend independently β the scrim dims the glyphs showing through it as well as the background behind them.
Stack a second AlphaBlend plane on top of that and you average again against the already-averaged result: 0x000000 blended onto 0x404040 gives 0x202020. So layers compound geometrically (Β½, ΒΌ, β
β¦) rather than letting you dial in an arbitrary fraction.
There is no continuous alpha β interpolate colours, not alpha
This is the single most important thing to internalise before you write a fade. You cannot animate AlphaOpaque β AlphaBlend through intermediate values, because there are none; the two bits either say blend or they don't. Anything that wants a smooth fade must interpolate colour:
# WRONG β there is no 30%-blend state to land on.
# my $a = lerp-alpha(AlphaOpaque, AlphaBlend, 0.3);
# RIGHT β hold the alpha mode fixed and lerp the RGB towards the
# background you're fading into.
sub lerp-rgb(UInt $from, UInt $to, Num() $t --> UInt) {
my @f = ($from +> 16) +& 0xFF, ($from +> 8) +& 0xFF, $from +& 0xFF;
my @t = ($to +> 16) +& 0xFF, ($to +> 8) +& 0xFF, $to +& 0xFF;
my @m = (^3).map: { (@f[$_] + (@t[$_] - @f[$_]) * $t).Int };
(@m[0] +< 16) +| (@m[1] +< 8) +| @m[2];
}
my $faded = Selkie::Style.new(fg => lerp-rgb(0xC0C0C0, 0x1A1A2E, $t));The same rule applies to "make the scrim darker": you do not add more alpha, you pick a darker scrim colour. A pure-black AlphaBlend scrim is as dark as one blend layer gets; going further means either a second layer or accepting the 50% and choosing your colours accordingly.
AlphaHighContrast is foreground-only
Notcurses rejects NCALPHA_HIGHCONTRAST outright on a background channel β ncchannels_set_bg_alpha returns -1 and the write is silently dropped. Selkie does not let you get that far: constructing a Selkie::Style with bg-alpha =E<gt> AlphaHighContrast throws, so the mistake surfaces at the construction site with a name attached rather than as a background that mysteriously refuses to change.
On a foreground it means "compute a colour that reads against whatever ends up behind this glyph" β useful for text over unpredictable content (an image, a gradient), and the one case where the style's own fg is ignored.
Transparent backgrounds and the glyph search
AlphaTransparent on a background makes the cell contribute no background colour. It does not make the cell's glyph disappear β a space is still a space, and it will still cover whatever is beneath it. The way to let a glyph from a lower plane show through is to give the cell no glyph at all: a base cell primed with the empty string has gcluster 0, which is the sentinel notcurses's glyph search treats as "keep looking further down the pile". That combination β empty EGC plus transparent or blended channels β is what makes a see-through overlay possible. See Selkie::Widget's base-egc hook.
EXAMPLES
Dimming a whole screen behind an overlay
# On the backdrop plane: no glyph of its own, black at 50%.
my $scrim = Selkie::Style.new(
fg => 0x000000, bg => 0x000000,
fg-alpha => AlphaBlend, bg-alpha => AlphaBlend,
);A label that keeps the panel's background
Leaving bg undefined inherits whatever background was last applied to the plane. AlphaTransparent is stronger: it removes this cell from the background computation entirely, so the plane beneath this one supplies the colour.
my $over-image = Selkie::Style.new(fg-alpha => AlphaHighContrast, bg-alpha => AlphaTransparent);SEE ALSO
Selkie::Style β carries
fg-alpha/bg-alphaand merges themSelkie::Widget β
apply-stylepushes them to the plane;base-style/base-egccontrol the base cell
The four alpha behaviours a channel can carry. Notcurses stores this in two bits per channel, so these four values are the entire space β there is nothing between AlphaOpaque and AlphaBlend. See the module Pod for what that means for fades. AlphaHighContrast is legal on a foreground only; Selkie::Style throws if you put it on a background.
sub alpha-constant
sub alpha-constant(
AlphaMode:D $mode
) returns UIntThe raw NCALPHA_* constant for an AlphaMode, ready to hand to ncplane_set_fg_alpha, ncchannels_set_bg_alpha, and friends. You only need this if you're calling the notcurses bindings directly; Selkie::Widget.apply-style does the translation for styled output, and base-style does it for the plane's base cell. alpha-constant(AlphaOpaque); # 0x00000000 alpha-constant(AlphaBlend); # 0x10000000 alpha-constant(AlphaTransparent); # 0x20000000 alpha-constant(AlphaHighContrast); # 0x30000000