The Over Operator and Forty Grams of Foam
Thomas Porter and Tom Duff published “Compositing Digital Images” at SIGGRAPH 1984, and every Photoshop layer mode, every video dissolve, every game engine’s transparency system descends from that paper. They formalized twelve operators for combining images with alpha channels — coverage masks that say “this pixel is 70% foreground, 30% background.”
The one that matters most is over: place the foreground atop the background, letting the background show through wherever the foreground is translucent.
struct RGBA { var r: Double, g: Double, b: Double, a: Double }
func over(_ fg: RGBA, _ bg: RGBA) -> RGBA {
let a = fg.a + bg.a * (1 - fg.a)
guard a > 0 else { return RGBA(r: 0, g: 0, b: 0, a: 0) }
return RGBA(
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
a: a)
}
let crema = RGBA(r: 0.55, g: 0.35, b: 0.18, a: 1.0) // opaque amber
let milk = RGBA(r: 1.0, g: 0.97, b: 0.94, a: 0.6) // translucent white
let blend = over(milk, crema)
print(String(format: "Blended: (%.2f, %.2f, %.2f)", blend.r, blend.g, blend.b))
// Blended: (0.82, 0.72, 0.64)
sub over {
my ($fg, $bg) = @_;
my $a = $fg->{a} + $bg->{a} * (1 - $fg->{a});
return {r=>0, g=>0, b=>0, a=>0} unless $a;
return {
r => ($fg->{r}*$fg->{a} + $bg->{r}*$bg->{a}*(1-$fg->{a})) / $a,
g => ($fg->{g}*$fg->{a} + $bg->{g}*$bg->{a}*(1-$fg->{a})) / $a,
b => ($fg->{b}*$fg->{a} + $bg->{b}*$bg->{a}*(1-$fg->{a})) / $a,
a => $a };
}
my $crema = {r=>0.55, g=>0.35, b=>0.18, a=>1.0};
my $milk = {r=>1.0, g=>0.97, b=>0.94, a=>0.6};
my $b = over($milk, $crema);
printf "Blended: (%.2f, %.2f, %.2f)\n", $b->{r}, $b->{g}, $b->{b};
Watch what happens in the cup: steamed milk sinks through the crema, but not uniformly. Dense microfoam — properly textured, no visible bubbles — has a different opacity than the thin milk that runs ahead of it. Where foam meets crema, you get partial coverage. The background (amber espresso) shows through in proportion to how much foreground (white milk) isn’t there. Pour quickly and the milk punches through, replacing the crema entirely — alpha approaches 1.0, background contribution drops to zero. Pour slowly, letting the foam spread, and you’re compositing at lower alpha values. The rosetta pattern emerges from differential opacity across the pour.
The formula itself is elegant: premultiply each colour by its alpha, blend, then divide back out to get the final RGB. Swift’s guard clause handles the degenerate case where both layers are fully transparent — division by zero lurks there otherwise. Perl’s unless $a does the same work with less ceremony.
Porter and Duff were solving a film production problem: how to composite live actors over matte paintings without edge artifacts. They probably didn’t anticipate their math describing the collision of two unstable colloid systems in a ceramic cup forty years later.