diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs
new file mode 100644
index 0000000000..6219380f70
--- /dev/null
+++ b/src/ImageSharp/Primitives/Complex64.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Primitives
+{
+ ///
+ /// Represents a complex number, where the real and imaginary parts are stored as values.
+ ///
+ ///
+ /// This is a more efficient version of the type.
+ ///
+ internal readonly struct Complex64
+ {
+ ///
+ /// The real part of the complex number
+ ///
+ public readonly float Real;
+
+ ///
+ /// The imaginary part of the complex number
+ ///
+ public readonly float Imaginary;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The real part in the complex number.
+ /// The imaginary part in the complex number.
+ public Complex64(float real, float imaginary)
+ {
+ this.Real = real;
+ this.Imaginary = imaginary;
+ }
+
+ ///
+ /// Performs the multiplication operation between a intance and a scalar.
+ ///
+ /// The value to multiply.
+ /// The scalar to use to multiply the value.
+ /// The result
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static Complex64 operator *(Complex64 value, float scalar) => new Complex64(value.Real * scalar, value.Imaginary * scalar);
+
+ ///
+ /// Performs the addition operation between two intances.
+ ///
+ /// The first value to sum.
+ /// The second value to sum.
+ /// The result
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static Complex64 operator +(Complex64 left, Complex64 right) => new Complex64(left.Real + right.Real, left.Imaginary + right.Imaginary);
+
+ ///
+ /// Performs a weighted sum on the current instance according to the given parameters
+ ///
+ /// The 'a' parameter, for the real component
+ /// The 'b' parameter, for the imaginary component
+ /// The resulting value
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public float WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b);
+ }
+}
diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs
index b598f0e02f..6b134bbfd7 100644
--- a/src/ImageSharp/Primitives/Rational.cs
+++ b/src/ImageSharp/Primitives/Rational.cs
@@ -102,7 +102,7 @@ public Rational(double value, bool bestPrecision)
/// Determines whether the specified instances are not considered equal.
///
/// The first to compare.
- /// The second to compare.
+ /// The second to compare.
/// The
public static bool operator !=(Rational left, Rational right)
{
diff --git a/src/ImageSharp/Processing/BokehBlurExtensions.cs b/src/ImageSharp/Processing/BokehBlurExtensions.cs
new file mode 100644
index 0000000000..9d7dd65f43
--- /dev/null
+++ b/src/ImageSharp/Processing/BokehBlurExtensions.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// Adds bokeh blurring extensions to the type.
+ ///
+ public static class BokehBlurExtensions
+ {
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The .
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source)
+ where TPixel : struct, IPixel
+ => source.ApplyProcessor(new BokehBlurProcessor());
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The 'radius' value representing the size of the area to sample.
+ /// The 'components' value representing the number of kernels to use to approximate the bokeh effect.
+ /// The .
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components)
+ where TPixel : struct, IPixel
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components));
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The 'radius' value representing the size of the area to sample.
+ /// The 'components' value representing the number of kernels to use to approximate the bokeh effect.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components), rectangle);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
new file mode 100644
index 0000000000..befc9eeec2
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
@@ -0,0 +1,237 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// Applies bokeh blur processing to the image.
+ ///
+ /// The pixel format.
+ internal class BokehBlurProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The maximum size of the kernel in either direction.
+ ///
+ private readonly int kernelSize;
+
+ ///
+ /// The number of components to use when applying the bokeh blur
+ ///
+ private readonly int componentsCount;
+
+ ///
+ /// The kernel components to use for the current instance
+ ///
+ private readonly IReadOnlyList> kernelParameters;
+
+ ///
+ /// The scaling factor for kernel values
+ ///
+ private readonly float kernelsScale;
+
+ ///
+ /// The complex kernels to use to apply the blur for the current instance
+ ///
+ private readonly IReadOnlyList complexKernels;
+
+ ///
+ /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances
+ ///
+ private static readonly Dictionary<(int, int), (IReadOnlyList>, float, IReadOnlyList)> Cache =
+ new Dictionary<(int, int), (IReadOnlyList>, float, IReadOnlyList)>();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ ///
+ ///
+ /// The number of components to use to approximate the original 2D bokeh blur convolution kernel.
+ ///
+ public BokehBlurProcessor(int radius = 32, int components = 2)
+ {
+ this.Radius = radius;
+ this.kernelSize = (radius * 2) + 1;
+ this.componentsCount = components;
+
+ // Reuse the initialized values from the cache, if possible
+ if (Cache.TryGetValue((radius, components), out (IReadOnlyList>, float, IReadOnlyList) info))
+ {
+ this.kernelParameters = info.Item1;
+ this.kernelsScale = info.Item2;
+ this.complexKernels = info.Item3;
+ }
+ else
+ {
+ // Initialize the complex kernels and parameters with the current arguments
+ (this.kernelParameters, this.kernelsScale) = this.GetParameters();
+ this.complexKernels = (
+ from component in this.kernelParameters
+ select this.CreateComplex1DKernel(component['a'], component['b'])).ToArray();
+ this.NormalizeKernels();
+
+ // Store them in the cache for future use
+ Cache.Add((radius, components), (this.kernelParameters, this.kernelsScale, this.complexKernels));
+ }
+ }
+
+ ///
+ /// Gets the Radius
+ ///
+ public int Radius { get; }
+
+ ///
+ /// Gets the kernel scales to adjust the component values in each kernel
+ ///
+ private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f };
+
+ ///
+ /// Gets the available bokeh blur kernel parameters
+ ///
+ private static IReadOnlyList KernelComponents { get; } = new[]
+ {
+ // 1 component
+ new[,] { { 0.862325f, 1.624835f, 0.767583f, 1.862321f } },
+
+ // 2 components
+ new[,]
+ {
+ { 0.886528f, 5.268909f, 0.411259f, -0.548794f },
+ { 1.960518f, 1.558213f, 0.513282f, 4.56111f }
+ },
+
+ // 3 components
+ new[,]
+ {
+ { 2.17649f, 5.043495f, 1.621035f, -2.105439f },
+ { 1.019306f, 9.027613f, -0.28086f, -0.162882f },
+ { 2.81511f, 1.597273f, -0.366471f, 10.300301f }
+ },
+
+ // 4 components
+ new[,]
+ {
+ { 4.338459f, 1.553635f, -5.767909f, 46.164397f },
+ { 3.839993f, 4.693183f, 9.795391f, -15.227561f },
+ { 2.791880f, 8.178137f, -3.048324f, 0.302959f },
+ { 1.342190f, 12.328289f, 0.010001f, 0.244650f }
+ },
+
+ // 5 components
+ new[,]
+ {
+ { 4.892608f, 1.685979f, -22.356787f, 85.91246f },
+ { 4.71187f, 4.998496f, 35.918936f, -28.875618f },
+ { 4.052795f, 8.244168f, -13.212253f, -1.578428f },
+ { 2.929212f, 11.900859f, 0.507991f, 1.816328f },
+ { 1.512961f, 16.116382f, 0.138051f, -0.01f }
+ },
+
+ // 6 components
+ new[,]
+ {
+ { 5.143778f, 2.079813f, -82.326596f, 111.231024f },
+ { 5.612426f, 6.153387f, 113.878661f, 58.004879f },
+ { 5.982921f, 9.802895f, 39.479083f, -162.028887f },
+ { 6.505167f, 11.059237f, -71.286026f, 95.027069f },
+ { 3.869579f, 14.81052f, 1.405746f, -3.704914f },
+ { 2.201904f, 19.032909f, -0.152784f, -0.107988f }
+ }
+ };
+
+ ///
+ /// Gets the kernel parameters and scaling factor for the current count value in the current instance
+ ///
+ private (IReadOnlyList> Parameters, float Scale) GetParameters()
+ {
+ // Prepare the kernel components
+ int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count));
+ float[,] parameters = KernelComponents[index];
+ var mapping = new IReadOnlyDictionary[parameters.GetLength(0)];
+ for (int i = 0; i < parameters.GetLength(0); i++)
+ {
+ mapping[i] = new Dictionary
+ {
+ ['a'] = parameters[i, 0],
+ ['b'] = parameters[i, 1],
+ ['A'] = parameters[i, 2],
+ ['B'] = parameters[i, 3]
+ };
+ }
+
+ // Return the components and the adjustment scale
+ return (mapping, KernelScales[index]);
+ }
+
+ ///
+ /// Creates a complex 1D kernel with the specified parameters
+ ///
+ /// The exponential parameter for each complex component
+ /// The angle component for each complex component
+ private Complex64[] CreateComplex1DKernel(float a, float b)
+ {
+ // Precompute the range values
+ float[] ax = Enumerable.Range(-this.Radius, this.Radius + 1).Select(
+ i =>
+ {
+ float value = i * this.kernelsScale * (1f / this.Radius);
+ return value * value;
+ }).ToArray();
+
+ // Compute the complex kernels
+ var kernel = new Complex64[this.kernelSize];
+ for (int i = 0; i < this.kernelSize; i++)
+ {
+ float
+ real = (float)(Math.Exp(-a * ax[i]) * Math.Cos(b * ax[i])),
+ imaginary = (float)(Math.Exp(-a * ax[i]) * Math.Sin(b * ax[i]));
+ kernel[i] = new Complex64(real, imaginary);
+ }
+
+ return kernel;
+ }
+
+ ///
+ /// Normalizes the kernels with respect to A * real + B * imaginary
+ ///
+ private void NormalizeKernels()
+ {
+ // Calculate the complex weighted sum
+ double total = 0;
+ foreach ((Complex64[] kernel, IReadOnlyDictionary param) in this.complexKernels.Zip(this.kernelParameters, (k, p) => (k, p)))
+ {
+ for (int i = 0; i < kernel.Length; i++)
+ {
+ for (int j = 0; j < kernel.Length; j++)
+ {
+ total +=
+ (param['A'] * ((kernel[i].Real * kernel[j].Real) - (kernel[i].Imaginary * kernel[j].Imaginary))) +
+ (param['B'] * ((kernel[i].Real * kernel[j].Imaginary) + (kernel[i].Imaginary * kernel[j].Real)));
+ }
+ }
+ }
+
+ // Normalize the kernels
+ float scalar = (float)(1f / Math.Sqrt(total));
+ foreach (Complex64[] kernel in this.complexKernels)
+ {
+ for (int i = 0; i < kernel.Length; i++)
+ {
+ kernel[i] = kernel[i] * scalar;
+ }
+ }
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => throw new NotImplementedException();
+ }
+}