Keying and arithmetic
Operations that ImageMagick users expect: making a color transparent, replacing a color, flood fill, arithmetic with a constant, and stacking several images. Each matches its ImageMagick option exactly at 8 and 16 bits. For green and blue screens there is also a chroma key with spill removal.
Color tolerance
Keying compares colors with ImageMagick's -fuzz distance on the stored values: a tolerance from 0 (the exact color) to 1 (every color). Alpha counts three times as much as each color channel, and colors count in proportion to how visible both pixels are, so two fully transparent pixels always match.
Make a color transparent
// Remove a green screen: within 0.15 of pure green becomes transparent, and alpha ramps back over the next 0.1 for soft edges.
photo.Apply(ops => ops.MakeTransparent(new Rgba8(0, 177, 64, 255), tolerance: 0.15f, softness: 0.1f), Ct);
Raise the tolerance until the screen is gone, then add softness for clean hair and edges.
MakeTransparent clears alpha where a pixel lies within the tolerance and keeps its color, as ImageMagick's -transparent does.
softness ramps alpha back up over the next band of distance, for antialiased edges; with no softness the result is ImageMagick's.
Key out a green or blue screen
// Key out a green screen by chroma, so its lighter and darker patches go too, soften the edge, and take the green cast off
// what is left; then put the result over a new background.
photo.Apply(ops => ops
.ChromaKey(new Rgba8(0, 177, 64, 255), similarity: 0.12f, blend: 0.06f)
.Despill(DespillColor.Green, mix: 0.5f), Ct);
backdrop.Apply(ops => ops.DrawImage(photo, new Point(0, 0)), Ct);
ChromaKey compares chroma alone: the distance between the Rec. 709 Cb and Cr of each pixel and of the screen color, as a share
of the largest such distance (green to magenta). Luma is left out, so lighter and darker patches of an unevenly lit screen stay
closer to the key than they do by MakeTransparent's RGB distance. similarity sets the distance that becomes fully
transparent and blend the band over which alpha returns to full. Pixels keep their color and their alpha is scaled, so an
already transparent pixel stays transparent. Types without alpha are left as they are.
Despill removes the screen's colored cast from what remains. It holds green (or blue, with DespillColor.Blue) down to a
limit taken from the other two channels: their average with mix 0, the larger of them with mix 1, which leaves neutral colors
alone. expand lowers the limit further for heavy spill. Despill works on any raster, keyed or not.
Replace a color
photo.Apply(ops => ops.ReplaceColor(Rgba8.White, new Rgba8(250, 245, 230, 255), tolerance: 0.05f), Ct);
Flood fill
// Fill the region around (10, 10) whose colors are close to its own, as a paint bucket does.
photo.Apply(ops => ops.FloodFill(new Point(10, 10), Rgba8.White, tolerance: 0.1f), Ct);
FloodFill fills the region connected to the seed whose colors lie within the tolerance of the seed's own. It spreads to the four
side neighbours by default, as ImageMagick does; Connectivity.Eight adds the diagonals. Its visited set, one bit a pixel, counts
against MaxTotalBytes. Inside a scope, the region is found within the scope's area.
Select by color
// Select by color, then edit only the selection: a magic wand from a point, and every pixel close to a color.
using Raster<Gray8> wand = photo.FloodMask(new Point(30, 30), tolerance: 0.12f);
using Raster<Gray8> reds = photo.ColorMask(new Rgba8(200, 40, 30, 255), tolerance: 0.2f);
photo.Apply(ops => ops
.Within(new Mask(wand), o => o.Blur(4))
.Within(new Mask(reds), o => o.Saturation(1.4f)), Ct);
ColorMask and FloodMask return a Gray8 mask, 255 inside the selection, for Within(Mask, …).
Arithmetic with a constant
// Lift the shadows with a log curve, then cap the highlights; values are on a 0 to 1 scale, as ImageMagick's -evaluate.
photo.Apply(ops => ops
.Arithmetic(PixelMath.Log, 3)
.Arithmetic(PixelMath.Min, 0.95f), Ct);
Arithmetic applies a PixelMath operator (add, subtract, multiply, divide, pow, log, min, max, set) to the chosen channels, on
values scaled 0 to 1 and clamped. 8-bit and 16-bit pixels are evaluated as 16-bit samples and rounded as ImageMagick's Q16 build
rounds them, so the result equals -evaluate exactly.
Stack images
// Average a burst of photos to smooth noise; the median removes things that appear in only a few frames.
using Raster mean = Raster.Stack([first, second, third], StackMode.Mean);
using Raster clean = Raster.Stack([first, second, third], StackMode.Median);
Raster.Stack combines the same channel of the same pixel across two or more rasters (StackMode: mean, median, min, max), as
ImageMagick's -evaluate-sequence does for mean, min and max. The median is taken per channel, as Photoshop's median stack does;
ImageMagick's picks the whole pixel whose intensity is the median. Stack works a few rows at a time, holding one row of each input.