Table of Contents

Filters

using Raster soft = photo.Derive(ops => ops.Blur(3), Ct);           // Gaussian, sigma in pixels
using Raster boxed = photo.Derive(ops => ops.BoxBlur(4), Ct);       // fast, radius in pixels
using Raster crisp = photo.Derive(ops => ops.Sharpen(0.8f, 1.5f), Ct); // unsharp mask: amount, sigma

// Any kernel: here a 3 × 3 emboss, with edges clamped and alpha left alone.
ConvolutionKernel emboss = new(3, 3, [-2, -1, 0, -1, 1, 1, 0, 1, 2]);
using Raster embossed = photo.Derive(ops => ops.Convolve(emboss, EdgeMode.Clamp, preserveAlpha: true), Ct);
Operation Does
Blur(sigma, colorSpace) Gaussian blur, separable; exact up to sigma 100, three box passes above
Blur(sigma, colorSpace, BlurMethod.Fast) Gaussian blur in constant time per pixel at any sigma, for shadows, glows and large blurs
BoxBlur(radius) Mean of a square, in constant time per pixel whatever the radius
Sharpen(amount, sigma) Unsharp mask: adds amount times the difference from a Gaussian blur
Convolve(kernel, edge, preserveAlpha) Any ConvolutionKernel, separable ones in two passes
Median(radius, includeAlpha) The median of each channel over a square: removes salt-and-pepper noise, keeps edges
LensBlur(radius, options) An out-of-focus lens: points become discs or polygons
BilateralBlur(spatialSigma, rangeSigma) Smooths flat areas, keeps edges
MotionBlur(radius, sigma, angle) A streak in one direction, as ImageMagick's -motion-blur
RotationalBlur(degrees, center) A spin around a point, as ImageMagick's -rotational-blur
ZoomBlur(amount, center) Streaks along rays from a point
AddNoise(kind, amount, monochrome, seed) ImageMagick's six noise kinds, the same for a seed at any thread count
Despeckle() Crimmins' speckle removal, as ImageMagick's -despeckle

Large blurs

Blur(sigma) computes the exact Gaussian, so its cost grows with sigma. BlurMethod.Fast runs three box passes per axis whose widths and fractional end weights give exactly the Gaussian's variance (Gwosdek et al., 2011): it costs the same at sigma 5 or 500, about six times less than the exact blur at sigma 20, and stays within 50 dB PSNR of it from sigma 3 up.

// A large, soft background blur: Fast costs the same at any sigma and is within 50 dB of the exact Gaussian from sigma 3.
using Raster soft = photo.Derive(ops => ops.Blur(40, ColorSpace.Linear, BlurMethod.Fast), Ct);

Median and despeckle

Median works on the stored values, so 8 and 16-bit results are exactly ImageMagick's -statistic Median. Its cost per pixel does not grow with the radius. Despeckle is gentler: it nudges values a level at a time toward their neighbours and leaves edges alone, exactly as ImageMagick's -despeckle.

// Salt-and-pepper noise disappears and edges stay sharp; radius 2 is a 5 × 5 window.
photo.Apply(ops => ops.Median(2), Ct);
// Clean a scan: specks shrink, edges and text stay. Median(1) is the stronger alternative.
scan.Apply(ops => ops.Despeckle(), Ct);

For heavy salt-and-pepper noise use Median(1) or Median(2) instead; both keep edges, and both give ImageMagick's pixels.

Lens and bilateral blur

LensBlur convolves with an aperture: a disc, or a polygon of 5 to 12 blades turned by Rotation. It works in linear light, since a lens adds light, and HighlightGain brightens light above HighlightThreshold before the blur, so small lights bloom into bright discs. It runs by FFT on tiles, so its cost does not grow with the radius.

// Blur the background like a wide-open lens with a six-blade aperture: bright points become hexagons.
LensBlurOptions lens = new() { Blades = 6, Rotation = 15, HighlightGain = 4, HighlightThreshold = 0.8f };
photo.Apply(ops => ops.Within(new Rect(0, 0, photo.Width, photo.Height / 3), o => o.LensBlur(12, lens)), Ct);

LensBlur works in linear light, so bright points stay bright discs instead of gray smudges; Within with a mask feathers the edge between the sharp subject and the blurred background.

BilateralBlur weighs each neighbour by its distance and by how different its color is, so noise flattens and edges stay. The color difference is the root-mean-square difference of red, green and blue on 0..1; on gray images the sigmas mean what ImageMagick's -bilateral-blur means, with its intensity sigma 255 times rangeSigma.

// Smooth skin or sky while keeping edges: neighbours count less the more their color differs (0.1 on the 0..1 scale).
using Raster smooth = photo.Derive(ops => ops.BilateralBlur(spatialSigma: 4, rangeSigma: 0.1f), Ct);

Directional blurs

MotionBlur takes ImageMagick's parameters: 2⌈radius⌉ + 1 samples along angle (clockwise, y down), weighted by a Gaussian of sigma. Egami reads between pixels bilinearly where ImageMagick rounds to the nearest pixel, so diagonal streaks are smoother. RotationalBlur and ZoomBlur take about one sample per pixel of path; their center is in pixels, with pixel centers at +0.5.

using Raster moving = photo.Derive(ops => ops.MotionBlur(radius: 12, sigma: 4, angle: 15), Ct);  // a streak toward 15°
using Raster spinning = photo.Derive(ops => ops.RotationalBlur(8), Ct);                          // 8° around the center
using Raster zooming = photo.Derive(ops => ops.ZoomBlur(0.2f, new System.Numerics.Vector2(320, 200)), Ct);

Noise

AddNoise adds ImageMagick's +noise kinds to red, green and blue, never alpha, with amount as ImageMagick's -attenuate. Each value's noise is a hash of the seed, frame and position, so the same seed gives the same pixels at any thread count, and inside Within an area gets the noise the whole image would get there.

// Film grain: gray Gaussian noise at a third of ImageMagick's default strength; the same seed gives the same grain.
photo.Apply(ops => ops.AddNoise(NoiseKind.Gaussian, 0.3f, monochrome: true, seed: 42), Ct);

The grain depends only on the seed and the pixel's position, so a batch of frames with the same seed grains alike.

Edges

EdgeMode says what a kernel sees past the border: Clamp repeats the edge pixel (the default), Mirror reflects the image, Wrap tiles it, and Transparent treats it as transparent black. The filters above repeat the edge pixel, as ImageMagick's default virtual pixel does.

Alpha

Blurs work on premultiplied values internally, so transparent pixels do not bleed their color into opaque ones. preserveAlpha: true keeps the alpha channel as it was, for effects that should only change color. Median and Despeckle keep alpha unless asked; AddNoise never touches it.

Run it

samples/Filters is a console app that does what this guide shows on the bundled images: dotnet run --project samples/Filters writes its results to ./output.