Selkie.git | docs/api/ | Selkie--Alpha.md


NAME
====

Selkie::Alpha - The four alpha modes a cell's foreground or background can carry

SYNOPSIS
========

```raku
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_BLEND
```

DESCRIPTION
===========

**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 own `fg`.

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:

```raku
# 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**:

```raku
# 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](Selkie--Style.md) 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](Selkie--Widget.md)'s `base-egc` hook.

EXAMPLES
========

Dimming a whole screen behind an overlay
----------------------------------------

```raku
# 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.

```raku
my $over-image = Selkie::Style.new(fg-alpha => AlphaHighContrast, bg-alpha => AlphaTransparent);
```

SEE ALSO
========

  * [Selkie::Style](Selkie--Style.md) — carries `fg-alpha` / `bg-alpha` and merges them

  * [Selkie::Widget](Selkie--Widget.md) — `apply-style` pushes them to the plane; `base-style` / `base-egc` control 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](Selkie--Style.md) throws if you put it on a background.

### sub alpha-constant

```raku
sub alpha-constant(
    AlphaMode:D $mode
) returns UInt
```

The 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