diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 2547e6d873..4bdbe088ca 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers.Binary;
-using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
new file mode 100644
index 0000000000..0c22aa68ff
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Extension methods for .
+ /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement.
+ ///
+ internal static class Buffer2DUtils
+ {
+ ///
+ /// Computes the sum of vectors in weighted by the kernel weight values.
+ ///
+ /// The pixel format.
+ /// The 1D convolution kernel.
+ /// The source frame.
+ /// The target row.
+ /// The current row.
+ /// The current column.
+ /// The minimum working area row.
+ /// The maximum working area row.
+ /// The minimum working area column.
+ /// The maximum working area column.
+ public static void Convolve4(
+ Span kernel,
+ Buffer2D sourcePixels,
+ Span targetRow,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
+ int maxColumn)
+ where TPixel : struct, IPixel
+ {
+ ComplexVector4 vector = default;
+ int kernelLength = kernel.Length;
+ int radiusY = kernelLength >> 1;
+ int sourceOffsetColumnBase = column + minColumn;
+ ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);
+
+ for (int i = 0; i < kernelLength; i++)
+ {
+ int offsetY = (row + i - radiusY).Clamp(minRow, maxRow);
+ int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn);
+ Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
+ var currentColor = sourceRowSpan[offsetX].ToVector4();
+
+ vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor);
+ }
+
+ targetRow[column] = vector;
+ }
+
+ ///
+ /// Computes the sum of vectors in weighted by the kernel weight values.
+ ///
+ /// The 1D convolution kernel.
+ /// The source frame.
+ /// The target row.
+ /// The current row.
+ /// The current column.
+ /// The minimum working area row.
+ /// The maximum working area row.
+ /// The minimum working area column.
+ /// The maximum working area column.
+ public static void Convolve4(
+ Span kernel,
+ Buffer2D sourceValues,
+ Span targetRow,
+ int row,
+ int column,
+ int minRow,
+ int maxRow,
+ int minColumn,
+ int maxColumn)
+ {
+ ComplexVector4 vector = default;
+ int kernelLength = kernel.Length;
+ int radiusX = kernelLength >> 1;
+ int sourceOffsetColumnBase = column + minColumn;
+
+ int offsetY = row.Clamp(minRow, maxRow);
+ ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY));
+ ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);
+
+ for (int x = 0; x < kernelLength; x++)
+ {
+ int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
+ vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX));
+ }
+
+ targetRow[column] = vector;
+ }
+ }
+}
diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
index 427b240057..c5c9ddebe1 100644
--- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
+++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -45,8 +45,6 @@ public static void Convolve2D3(
int maxColumn)
where TPixel : struct, IPixel
{
- Vector4 vector = default;
-
Convolve2DImpl(
in matrixY,
in matrixX,
@@ -57,7 +55,7 @@ public static void Convolve2D3(
maxRow,
minColumn,
maxColumn,
- ref vector);
+ out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
@@ -95,8 +93,6 @@ public static void Convolve2D4(
int maxColumn)
where TPixel : struct, IPixel
{
- Vector4 vector = default;
-
Convolve2DImpl(
in matrixY,
in matrixX,
@@ -107,7 +103,7 @@ public static void Convolve2D4(
maxRow,
minColumn,
maxColumn,
- ref vector);
+ out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
Vector4Utils.UnPremultiply(ref vector);
@@ -125,7 +121,7 @@ public static void Convolve2DImpl(
int maxRow,
int minColumn,
int maxColumn,
- ref Vector4 vector)
+ out Vector4 vector)
where TPixel : struct, IPixel
{
Vector4 vectorY = default;
@@ -281,4 +277,4 @@ private static void ConvolveImpl(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
index 85c9f00748..b16bbc86b6 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs
index 867e7b9de1..45761a0d07 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs
@@ -7,9 +7,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Tuples;
-
namespace SixLabors.ImageSharp
{
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
index ace8d7215b..2f393fadae 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Collections.Generic;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
index 301079b6ae..883a085b5a 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
@@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
index c795ccc8b5..3d1e22a99d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
@@ -7,7 +7,6 @@
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index 61fcb99db2..096493f2ba 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs
index 41a560cdb6..82a98bfc63 100644
--- a/src/ImageSharp/Memory/Buffer2D{T}.cs
+++ b/src/ImageSharp/Memory/Buffer2D{T}.cs
@@ -61,13 +61,31 @@ public Buffer2D(MemorySource memorySource, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
- ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
- ImageSharp.DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
+ DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
+ DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
+
Span span = this.Span;
return ref span[(this.Width * y) + x];
}
}
+ ///
+ /// Creates a new instance that maps to a target rows interval from the current instance.
+ ///
+ /// The target vertical offset for the rows interval to retrieve.
+ /// The desired number of rows to extract.
+ /// The new instance with the requested rows interval.
+ public Buffer2D Slice(int y, int h)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
+ DebugGuard.MustBeGreaterThan(h, 0, nameof(h));
+ DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h));
+
+ Memory slice = this.Memory.Slice(y * this.Width, h * this.Width);
+ var memory = new MemorySource(slice);
+ return new Buffer2D(memory, this.Width, h);
+ }
+
///
/// Disposes the instance
///
@@ -98,4 +116,4 @@ private static void SwapDimensionData(Buffer2D a, Buffer2D b)
a.Height = bSize.Height;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
index 7d694bec6e..bbfe909e01 100644
--- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc
{
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs
index 195a8bedb0..dd12074352 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using System.Numerics;
using System.Runtime.CompilerServices;
diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs
index 3da5d1855b..da02f374e1 100644
--- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs
+++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs
@@ -6,7 +6,6 @@
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats.Utils;
-using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats
{
diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
index 2b2d79c732..f557f348a1 100644
--- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
+++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
@@ -4,8 +4,6 @@
using System;
using System.Buffers;
using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs
index 4c4e60276d..e67bd9d53e 100644
--- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs
+++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs
@@ -6,8 +6,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.ColorSpaces.Companding;
-
namespace SixLabors.ImageSharp.PixelFormats.Utils
{
///
diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
index fe8d7dec3b..0350c669ab 100644
--- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
+++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
@@ -7,8 +7,6 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.ColorSpaces.Companding;
-
namespace SixLabors.ImageSharp.PixelFormats.Utils
{
///
diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs
new file mode 100644
index 0000000000..75905a9bd7
--- /dev/null
+++ b/src/ImageSharp/Primitives/Complex64.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+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 : IEquatable
+ {
+ ///
+ /// 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 multiplication operation between a intance and a .
+ ///
+ /// The value to multiply.
+ /// The instance to use to multiply the value.
+ /// The result
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static ComplexVector4 operator *(Complex64 value, Vector4 vector)
+ {
+ return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary };
+ }
+
+ ///
+ /// Performs the multiplication operation between a intance and a .
+ ///
+ /// The value to multiply.
+ /// The instance to use to multiply the value.
+ /// The result
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector)
+ {
+ Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary);
+ Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real);
+ return new ComplexVector4 { Real = real, Imaginary = imaginary };
+ }
+
+ ///
+ public bool Equals(Complex64 other)
+ {
+ return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary);
+ }
+
+ ///
+ public override bool Equals(object obj) => obj is Complex64 other && this.Equals(other);
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode();
+ }
+ }
+
+ ///
+ public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j";
+ }
+}
diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs
new file mode 100644
index 0000000000..b90da65b2d
--- /dev/null
+++ b/src/ImageSharp/Primitives/ComplexVector4.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Primitives
+{
+ ///
+ /// A vector with 4 values of type .
+ ///
+ internal struct ComplexVector4 : IEquatable
+ {
+ ///
+ /// The real part of the complex vector
+ ///
+ public Vector4 Real;
+
+ ///
+ /// The imaginary part of the complex number
+ ///
+ public Vector4 Imaginary;
+
+ ///
+ /// Sums the values in the input to the current instance
+ ///
+ /// The input to sum
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Sum(in ComplexVector4 value)
+ {
+ this.Real += value.Real;
+ this.Imaginary += value.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 Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b);
+
+ ///
+ public bool Equals(ComplexVector4 other)
+ {
+ return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary);
+ }
+
+ ///
+ public override bool Equals(object obj) => obj is ComplexVector4 other && this.Equals(other);
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/BokehBlurExecutionMode.cs b/src/ImageSharp/Processing/BokehBlurExecutionMode.cs
new file mode 100644
index 0000000000..bc44dca03c
--- /dev/null
+++ b/src/ImageSharp/Processing/BokehBlurExecutionMode.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// An that indicates execution options for the .
+ ///
+ public enum BokehBlurExecutionMode
+ {
+ ///
+ /// Indicates that the maximum performance should be prioritized over memory usage.
+ ///
+ PreferMaximumPerformance,
+
+ ///
+ /// Indicates that the memory usage should be prioritized over raw performance.
+ ///
+ PreferLowMemoryUsage
+ }
+}
diff --git a/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs
index a831e2d9af..984081dffe 100644
--- a/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing
diff --git a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs
index ee34cd99e2..c148ccbcb9 100644
--- a/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs
new file mode 100644
index 0000000000..ef20f940af
--- /dev/null
+++ b/src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs
@@ -0,0 +1,106 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+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 image this method extends.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source)
+ => source.ApplyProcessor(new BokehBlurProcessor());
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The image this method extends.
+ /// The execution mode to use when applying the processor.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, BokehBlurExecutionMode executionMode)
+ => source.ApplyProcessor(new BokehBlurProcessor(executionMode));
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// 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 gamma highlight factor to use to emphasize bright spots in the source image
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma)
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma));
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// 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 gamma highlight factor to use to emphasize bright spots in the source image
+ /// The execution mode to use when applying the processor.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode)
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode));
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The image this method extends.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle)
+ => source.ApplyProcessor(new BokehBlurProcessor(), rectangle);
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// The image this method extends.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The execution mode to use when applying the processor.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, BokehBlurExecutionMode executionMode)
+ => source.ApplyProcessor(new BokehBlurProcessor(executionMode), rectangle);
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// 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 gamma highlight factor to use to emphasize bright spots in the source image
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle)
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle);
+
+ ///
+ /// Applies a bokeh blur to the image.
+ ///
+ /// 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 gamma highlight factor to use to emphasize bright spots in the source image
+ /// The execution mode to use when applying the processor.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode, Rectangle rectangle)
+ => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode), rectangle);
+ }
+}
diff --git a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs
index f3400c24e6..42dfd425cc 100644
--- a/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs
@@ -1,7 +1,6 @@
// 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;
diff --git a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs
index db84091763..8e43f06c5a 100644
--- a/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs
index 5a66502ce5..662e3a6e16 100644
--- a/src/ImageSharp/Processing/Extensions/FilterExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/FilterExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs
index e527a14b73..858e3213b1 100644
--- a/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs
@@ -1,7 +1,6 @@
// 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;
diff --git a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs
index a87341025d..a4bfaa516c 100644
--- a/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/HueExtensions.cs
index 3c1239da67..3955ea7f6e 100644
--- a/src/ImageSharp/Processing/Extensions/HueExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/HueExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs
index c45f24c2ea..16c7a89178 100644
--- a/src/ImageSharp/Processing/Extensions/InvertExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/InvertExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs
index 810094a180..6c9b279835 100644
--- a/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs
index dd7ab21ec1..c2b6ac0804 100644
--- a/src/ImageSharp/Processing/Extensions/LomographExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/LomographExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs
index ecf6ce783e..9c67113ecf 100644
--- a/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/OpacityExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs
index eace463579..6b6d43d5b8 100644
--- a/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
index 4578b4353f..81b1c2c663 100644
--- a/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs
index 4d5d90c30e..0e4ad4066c 100644
--- a/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing
{
///
diff --git a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs
index e9ba820b6c..a94a9a407d 100644
--- a/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/SaturateExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs
index 5ee5151fae..df32307f47 100644
--- a/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/SepiaExtensions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
index 8129836641..840d1c1f46 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
index 83701aa8a2..7f00d0219d 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
new file mode 100644
index 0000000000..7a750bdd4a
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// Applies bokeh blur processing to the image.
+ ///
+ public sealed class BokehBlurProcessor : IImageProcessor
+ {
+ ///
+ /// The default radius used by the parameterless constructor.
+ ///
+ public const int DefaultRadius = 32;
+
+ ///
+ /// The default component count used by the parameterless constructor.
+ ///
+ public const int DefaultComponents = 2;
+
+ ///
+ /// The default gamma used by the parameterless constructor.
+ ///
+ public const float DefaultGamma = 3F;
+
+ ///
+ /// The default execution mode used by the parameterless constructor.
+ ///
+ public const BokehBlurExecutionMode DefaultExecutionMode = BokehBlurExecutionMode.PreferLowMemoryUsage;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BokehBlurProcessor()
+ : this(DefaultRadius, DefaultComponents, DefaultGamma, DefaultExecutionMode)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The execution mode to use when applying the processor.
+ ///
+ public BokehBlurProcessor(BokehBlurExecutionMode executionMode)
+ : this(DefaultRadius, DefaultComponents, DefaultGamma, executionMode)
+ {
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// The gamma highlight factor to use to further process the image.
+ ///
+ public BokehBlurProcessor(int radius, int components, float gamma)
+ : this(radius, components, gamma, DefaultExecutionMode)
+ {
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// The gamma highlight factor to use to further process the image.
+ ///
+ ///
+ /// The execution mode to use when applying the processor.
+ ///
+ public BokehBlurProcessor(int radius, int components, float gamma, BokehBlurExecutionMode executionMode)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma));
+
+ this.Radius = radius;
+ this.Components = components;
+ this.Gamma = gamma;
+ this.ExecutionMode = executionMode;
+ }
+
+ ///
+ /// Gets the radius.
+ ///
+ public int Radius { get; }
+
+ ///
+ /// Gets the number of components.
+ ///
+ public int Components { get; }
+
+ ///
+ /// Gets the gamma highlight factor to use when applying the effect.
+ ///
+ public float Gamma { get; }
+
+ ///
+ /// Gets the exection mode to use when applying the effect.
+ ///
+ public BokehBlurExecutionMode ExecutionMode { get; }
+
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor()
+ where TPixel : struct, IPixel
+ {
+ return new BokehBlurProcessor(this);
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
new file mode 100644
index 0000000000..a083026c37
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
@@ -0,0 +1,585 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// Applies bokeh blur processing to the image.
+ ///
+ /// The pixel format.
+ /// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve.
+ internal class BokehBlurProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The kernel radius.
+ ///
+ private readonly int radius;
+
+ ///
+ /// The gamma highlight factor to use when applying the effect
+ ///
+ private readonly float gamma;
+
+ ///
+ /// The execution mode to use when applying the effect
+ ///
+ private readonly BokehBlurExecutionMode executionMode;
+
+ ///
+ /// 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 parameters to use for the current instance (a: X, b: Y, A: Z, B: W)
+ ///
+ private readonly Vector4[] kernelParameters;
+
+ ///
+ /// The kernel components for the current instance
+ ///
+ private readonly Complex64[][] kernels;
+
+ ///
+ /// The scaling factor for kernel values
+ ///
+ private readonly float kernelsScale;
+
+ ///
+ /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances
+ ///
+ private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The defining the processor parameters.
+ public BokehBlurProcessor(BokehBlurProcessor definition)
+ {
+ this.radius = definition.Radius;
+ this.kernelSize = (this.radius * 2) + 1;
+ this.componentsCount = definition.Components;
+ this.gamma = definition.Gamma;
+ this.executionMode = definition.ExecutionMode;
+
+ // Reuse the initialized values from the cache, if possible
+ var parameters = new BokehBlurParameters(this.radius, this.componentsCount);
+ if (Cache.TryGetValue(parameters, out BokehBlurKernelData info))
+ {
+ this.kernelParameters = info.Parameters;
+ this.kernelsScale = info.Scale;
+ this.kernels = info.Kernels;
+ }
+ else
+ {
+ // Initialize the complex kernels and parameters with the current arguments
+ (this.kernelParameters, this.kernelsScale) = this.GetParameters();
+ this.kernels = this.CreateComplexKernels();
+ this.NormalizeKernels();
+
+ // Store them in the cache for future use
+ Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels));
+ }
+ }
+
+ ///
+ /// Gets the complex kernels to use to apply the blur for the current instance
+ ///
+ public IReadOnlyList Kernels => this.kernels;
+
+ ///
+ /// Gets the kernel parameters used to compute the pixel values from each complex pixel
+ ///
+ public IReadOnlyList KernelParameters => this.kernelParameters;
+
+ ///
+ /// 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[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) },
+
+ // 2 components
+ new[]
+ {
+ new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f),
+ new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f)
+ },
+
+ // 3 components
+ new[]
+ {
+ new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f),
+ new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f),
+ new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f)
+ },
+
+ // 4 components
+ new[]
+ {
+ new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f),
+ new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f),
+ new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f),
+ new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f)
+ },
+
+ // 5 components
+ new[]
+ {
+ new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f),
+ new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f),
+ new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f),
+ new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f),
+ new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f)
+ },
+
+ // 6 components
+ new[]
+ {
+ new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f),
+ new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f),
+ new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f),
+ new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f),
+ new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f),
+ new Vector4(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 (Vector4[] Parameters, float Scale) GetParameters()
+ {
+ // Prepare the kernel components
+ int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count));
+ return (KernelComponents[index], KernelScales[index]);
+ }
+
+ ///
+ /// Creates the collection of complex 1D kernels with the specified parameters
+ ///
+ private Complex64[][] CreateComplexKernels()
+ {
+ var kernels = new Complex64[this.kernelParameters.Length][];
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
+ for (int i = 0; i < this.kernelParameters.Length; i++)
+ {
+ ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i);
+ kernels[i] = this.CreateComplex1DKernel(paramsRef.X, paramsRef.Y);
+ }
+
+ return kernels;
+ }
+
+ ///
+ /// 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)
+ {
+ var kernel = new Complex64[this.kernelSize];
+ ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan());
+ int r = this.radius, n = -r;
+
+ for (int i = 0; i < this.kernelSize; i++, n++)
+ {
+ // Incrementally compute the range values
+ float value = n * this.kernelsScale * (1f / r);
+ value *= value;
+
+ // Fill in the complex kernel values
+ Unsafe.Add(ref baseRef, i) = new Complex64(
+ MathF.Exp(-a * value) * MathF.Cos(b * value),
+ MathF.Exp(-a * value) * MathF.Sin(b * value));
+ }
+
+ return kernel;
+ }
+
+ ///
+ /// Normalizes the kernels with respect to A * real + B * imaginary
+ ///
+ private void NormalizeKernels()
+ {
+ // Calculate the complex weighted sum
+ float total = 0;
+ Span kernelsSpan = this.kernels.AsSpan();
+ ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan);
+ ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
+
+ for (int i = 0; i < this.kernelParameters.Length; i++)
+ {
+ ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i);
+ int length = kernelRef.Length;
+ ref Complex64 valueRef = ref kernelRef[0];
+ ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i);
+
+ for (int j = 0; j < length; j++)
+ {
+ for (int k = 0; k < length; k++)
+ {
+ ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j);
+ ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k);
+ total +=
+ (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary)))
+ + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real)));
+ }
+ }
+ }
+
+ // Normalize the kernels
+ float scalar = 1f / MathF.Sqrt(total);
+ for (int i = 0; i < kernelsSpan.Length; i++)
+ {
+ ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i);
+ int length = kernelsRef.Length;
+ ref Complex64 valueRef = ref kernelsRef[0];
+
+ for (int j = 0; j < length; j++)
+ {
+ Unsafe.Add(ref valueRef, j) *= scalar;
+ }
+ }
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ // Preliminary gamma highlight pass
+ this.ApplyGammaExposure(source.PixelBuffer, sourceRectangle, configuration);
+
+ // Create a 0-filled buffer to use to store the result of the component convolutions
+ using (Buffer2D processing = configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean))
+ {
+ if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage)
+ {
+ // Memory usage priority: allocate a shared buffer and execute the second convolution in sequential mode
+ using (Buffer2D buffer = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height + this.radius))
+ using (Buffer2D firstPassBuffer = buffer.Slice(this.radius, source.Height))
+ using (Buffer2D secondPassBuffer = buffer.Slice(0, source.Height))
+ {
+ this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassBuffer, secondPassBuffer);
+ }
+ }
+ else
+ {
+ // Performance priority: allocate two independent buffers and execute both convolutions in parallel mode
+ using (Buffer2D firstPassValues = configuration.MemoryAllocator.Allocate2D(source.Size()))
+ using (Buffer2D secondPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size()))
+ {
+ this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassValues, secondPassBuffer);
+ }
+ }
+
+ // Apply the inverse gamma exposure pass, and write the final pixel data
+ this.ApplyInverseGammaExposure(source.PixelBuffer, processing, sourceRectangle, configuration);
+ }
+ }
+
+ ///
+ /// Computes and aggregates the convolution for each complex kernel component in the processor.
+ ///
+ /// The source image. Cannot be null.
+ /// The structure that specifies the portion of the image object to draw.
+ /// The configuration.
+ /// The buffer with the raw pixel data to use to aggregate the results of each convolution.
+ /// The complex buffer to use for the first 1D convolution pass for each kernel.
+ /// The complex buffer to use for the second 1D convolution pass for each kernel.
+ private void OnFrameApplyCore(
+ ImageFrame source,
+ Rectangle sourceRectangle,
+ Configuration configuration,
+ Buffer2D processingBuffer,
+ Buffer2D firstPassBuffer,
+ Buffer2D secondPassBuffer)
+ {
+ // Perform two 1D convolutions for each component in the current instance
+ ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan());
+ for (int i = 0; i < this.kernels.Length; i++)
+ {
+ // Compute the resulting complex buffer for the current component
+ var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
+ Complex64[] kernel = Unsafe.Add(ref baseRef, i);
+ this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration);
+ this.ApplyConvolution(secondPassBuffer, firstPassBuffer, interest, kernel, configuration);
+
+ // Add the results of the convolution with the current kernel
+ Vector4 parameters = this.kernelParameters[i];
+ this.SumProcessingPartials(processingBuffer, secondPassBuffer, sourceRectangle, configuration, parameters.Z, parameters.W);
+ }
+ }
+
+ ///
+ /// Applies the process to the specified portion of the specified at the specified location
+ /// and with the specified size.
+ ///
+ /// The target values to use to store the results.
+ /// The source pixels. Cannot be null.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The 1D kernel.
+ /// The
+ private void ApplyConvolution(
+ Buffer2D targetValues,
+ Buffer2D sourcePixels,
+ Rectangle sourceRectangle,
+ Complex64[] kernel,
+ Configuration configuration)
+ {
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int maxY = endY - 1;
+ int maxX = endX - 1;
+
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+ int width = workingRectangle.Width;
+
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
+
+ for (int x = 0; x < width; x++)
+ {
+ Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX);
+ }
+ }
+ });
+ }
+
+ ///
+ /// Applies the process to the specified portion of the specified buffer at the specified location
+ /// and with the specified size.
+ ///
+ /// The target values to use to store the results.
+ /// The source complex values. Cannot be null.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The 1D kernel.
+ /// The
+ private void ApplyConvolution(
+ Buffer2D targetValues,
+ Buffer2D sourceValues,
+ Rectangle sourceRectangle,
+ Complex64[] kernel,
+ Configuration configuration)
+ {
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int maxY = endY - 1;
+ int maxX = endX - 1;
+
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+ int width = workingRectangle.Width;
+
+ if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage)
+ {
+ configuration = configuration.Clone();
+ configuration.MaxDegreeOfParallelism = 1;
+ }
+
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
+
+ for (int x = 0; x < width; x++)
+ {
+ Buffer2DUtils.Convolve4(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX);
+ }
+ }
+ });
+ }
+
+ ///
+ /// Applies the gamma correction/highlight to the input pixel buffer.
+ ///
+ /// The target pixel buffer to adjust.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The
+ private void ApplyGammaExposure(
+ Buffer2D targetPixels,
+ Rectangle sourceRectangle,
+ Configuration configuration)
+ {
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+ int width = workingRectangle.Width;
+ float exp = this.gamma;
+
+ ParallelHelper.IterateRowsWithTempBuffer(
+ workingRectangle,
+ configuration,
+ (rows, vectorBuffer) =>
+ {
+ Span vectorSpan = vectorBuffer.Span;
+ int length = vectorSpan.Length;
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
+ PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply);
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan);
+
+ for (int x = 0; x < width; x++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
+ v.X = MathF.Pow(v.X, exp);
+ v.Y = MathF.Pow(v.Y, exp);
+ v.Z = MathF.Pow(v.Z, exp);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
+ }
+ });
+ }
+
+ ///
+ /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values.
+ ///
+ /// The target pixels to apply the process to.
+ /// The source values. Cannot be null.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The
+ private void ApplyInverseGammaExposure(
+ Buffer2D targetPixels,
+ Buffer2D sourceValues,
+ Rectangle sourceRectangle,
+ Configuration configuration)
+ {
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+ int width = workingRectangle.Width;
+ float expGamma = 1 / this.gamma;
+
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
+ {
+ Vector4 low = Vector4.Zero;
+ var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
+
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX);
+ Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX);
+ ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
+
+ for (int x = 0; x < width; x++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
+ var clamp = Vector4.Clamp(v, low, high);
+ v.X = MathF.Pow(clamp.X, expGamma);
+ v.Y = MathF.Pow(clamp.Y, expGamma);
+ v.Z = MathF.Pow(clamp.Z, expGamma);
+ }
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply);
+ }
+ });
+ }
+
+ ///
+ /// Applies the process to the specified portion of the specified at the specified location
+ /// and with the specified size.
+ ///
+ /// The target instance to use to store the results.
+ /// The source complex pixels. Cannot be null.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The
+ /// The weight factor for the real component of the complex pixel values.
+ /// The weight factor for the imaginary component of the complex pixel values.
+ private void SumProcessingPartials(
+ Buffer2D targetValues,
+ Buffer2D sourceValues,
+ Rectangle sourceRectangle,
+ Configuration configuration,
+ float z,
+ float w)
+ {
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+
+ var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
+ int width = workingRectangle.Width;
+
+ ParallelHelper.IterateRows(
+ workingRectangle,
+ configuration,
+ rows =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
+ Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX);
+ ref Vector4 baseTargetRef = ref MemoryMarshal.GetReference(targetRowSpan);
+ ref ComplexVector4 baseSourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
+
+ for (int x = 0; x < width; x++)
+ {
+ Unsafe.Add(ref baseTargetRef, x) += Unsafe.Add(ref baseSourceRef, x).WeightedSum(z, w);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
index 4e56e75d39..726947ac91 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
@@ -1,8 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
@@ -47,4 +46,4 @@ public IImageProcessor CreatePixelSpecificProcessor()
return new BoxBlurProcessor(this);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
index 764f4ca517..8504db1617 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
index 23282af36d..75acc90e0c 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
index 9c9488fec0..ab6658f3b2 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs
new file mode 100644
index 0000000000..4338bcf6b9
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Numerics;
+
+using SixLabors.ImageSharp.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
+{
+ ///
+ /// A that contains data about a set of bokeh blur kernels
+ ///
+ internal readonly struct BokehBlurKernelData
+ {
+ ///
+ /// The kernel parameters to use for the current set of complex kernels
+ ///
+ public readonly Vector4[] Parameters;
+
+ ///
+ /// The scaling factor for the kernel values
+ ///
+ public readonly float Scale;
+
+ ///
+ /// The kernel components to apply the bokeh blur effect
+ ///
+ public readonly Complex64[][] Kernels;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The kernel parameters
+ /// The kernel scale factor
+ /// The complex kernel components
+ public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels)
+ {
+ this.Parameters = parameters;
+ this.Scale = scale;
+ this.Kernels = kernels;
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs
new file mode 100644
index 0000000000..73688c5869
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
+{
+ ///
+ /// A that contains parameters to apply a bokeh blur filter
+ ///
+ internal readonly struct BokehBlurParameters : IEquatable
+ {
+ ///
+ /// The size of the convolution kernel to use when applying the bokeh blur
+ ///
+ public readonly int Radius;
+
+ ///
+ /// The number of complex components to use to approximate the bokeh kernel
+ ///
+ public readonly int Components;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The size of the kernel
+ /// The number of kernel components
+ public BokehBlurParameters(int radius, int components)
+ {
+ this.Radius = radius;
+ this.Components = components;
+ }
+
+ ///
+ public bool Equals(BokehBlurParameters other)
+ {
+ return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components);
+ }
+
+ ///
+ public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other);
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
index a9db37a076..ca7d8895a8 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
index fd73789027..6f5373fae5 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
@@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Primitives;
-
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
index ec0183dc63..da76aa971c 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
index 3d5d1e7bf1..5fb32f4e62 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
@@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
-
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
///
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
index e0b79c2b20..e612b4bf03 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
@@ -3,8 +3,6 @@
using System;
-using SixLabors.ImageSharp.Processing.Processors.Binarization;
-
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
///
diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
index ae6d5f6f79..71d3f9c9cc 100644
--- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
@@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.Primitives;
-
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
///
diff --git a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs
index 012b10ee03..30484f0590 100644
--- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
///
diff --git a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs
index 922ca32330..a537b8f606 100644
--- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
-
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
///
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
index 3f6dfde281..c912572f0e 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
index d659ecabf7..cd320a9a36 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
index 9bbbba843c..3eb0d998a2 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
index dce4e70d62..14bf552b9d 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
@@ -4,7 +4,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
index 4b81aaa64e..6d6e22a6a5 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
index cb30067401..99178b34cc 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
@@ -2,12 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Linq;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index cb73bb66c2..4b87d6d2c1 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -3,7 +3,6 @@
using System.Numerics;
-using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
index ef508549ac..f6d3299fcd 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
diff --git a/src/ImageSharp/Properties/AssemblyInfo.cs b/src/ImageSharp/Properties/AssemblyInfo.cs
index 2862b45851..225de354ae 100644
--- a/src/ImageSharp/Properties/AssemblyInfo.cs
+++ b/src/ImageSharp/Properties/AssemblyInfo.cs
@@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Runtime.CompilerServices;
-
// Redundant suppressing of SA1413 for Rider.
[assembly:
System.Diagnostics.CodeAnalysis.SuppressMessage(
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
new file mode 100644
index 0000000000..3796c51a3b
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
@@ -0,0 +1,156 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
+using SixLabors.Primitives;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
+{
+ public class BokehBlurTest
+ {
+ private static readonly string Components10x2 = @"
+ [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j
+ -0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j
+ 0.01747482+0.04687464j 0.04243676+0.03451751j 0.05564306+0.01742537j
+ 0.06040984+0.00459225j 0.06136251+0.0j 0.06040984+0.00459225j
+ 0.05564306+0.01742537j 0.04243676+0.03451751j 0.01747482+0.04687464j
+ -0.01428268+0.04224722j -0.03553877+0.0154543j -0.02752798-0.01788438j
+ 0.00387479-0.02682816j 0.02161237-0.00299122j 0.00451261+0.0165137j ]]
+ [[-0.00227282+0.002851j -0.00152245+0.00604545j 0.00135338+0.00998296j
+ 0.00698622+0.01370844j 0.0153483+0.01605112j 0.02565295+0.01611732j
+ 0.03656958+0.01372368j 0.04662725+0.00954624j 0.05458942+0.00491277j
+ 0.05963937+0.00133843j 0.06136251+0.0j 0.05963937+0.00133843j
+ 0.05458942+0.00491277j 0.04662725+0.00954624j 0.03656958+0.01372368j
+ 0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j
+ 0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]";
+
+ [Fact]
+ public void VerifyComplexComponents()
+ {
+ // Get the saved components
+ var components = new List();
+ foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline))
+ {
+ string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ Complex64[] component = values.Select(
+ value =>
+ {
+ Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j");
+ return new Complex64(
+ float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture),
+ float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture));
+ }).ToArray();
+ components.Add(component);
+ }
+
+ // Make sure the kernel components are the same
+ var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma);
+ var processor = new BokehBlurProcessor(definition);
+ Assert.Equal(components.Count, processor.Kernels.Count);
+ foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b)))
+ {
+ Span spanA = a.AsSpan(), spanB = b.AsSpan();
+ Assert.Equal(spanA.Length, spanB.Length);
+ for (int i = 0; i < spanA.Length; i++)
+ {
+ Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f);
+ Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f);
+ }
+ }
+ }
+
+ public sealed class BokehBlurInfo : IXunitSerializable
+ {
+ public int Radius { get; set; }
+
+ public int Components { get; set; }
+
+ public float Gamma { get; set; }
+
+ public void Deserialize(IXunitSerializationInfo info)
+ {
+ this.Radius = info.GetValue(nameof(this.Radius));
+ this.Components = info.GetValue(nameof(this.Components));
+ this.Gamma = info.GetValue(nameof(this.Gamma));
+ }
+
+ public void Serialize(IXunitSerializationInfo info)
+ {
+ info.AddValue(nameof(this.Radius), this.Radius, typeof(int));
+ info.AddValue(nameof(this.Components), this.Components, typeof(int));
+ info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float));
+ }
+
+ public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}";
+ }
+
+ public static readonly TheoryData BokehBlurValues = new TheoryData
+ {
+ new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 },
+ new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 },
+ new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 }
+ };
+
+ public static readonly string[] TestFiles =
+ {
+ TestImages.Png.CalliphoraPartial,
+ TestImages.Png.Bike,
+ TestImages.Png.BikeGrayscale,
+ TestImages.Png.Cross,
+ };
+
+ [Theory]
+ [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)]
+ [WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)]
+ [WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)]
+ [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)]
+ [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)]
+ public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value)
+ where TPixel : struct, IPixel
+ {
+ provider.RunValidatingProcessorTest(
+ x => x.BokehBlur(value.Radius, value.Components, value.Gamma),
+ testOutputDetails: value.ToString(),
+ appendPixelTypeToFileName: false);
+ }
+
+ [Theory]
+ [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.Gray8)]
+ public void BokehBlurFilterProcessor_WorksWithAllPixelTypes(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.RunValidatingProcessorTest(
+ x => x.BokehBlur(8, 2, 3),
+ appendSourceFileOrDescription: false);
+ }
+
+
+ [Theory]
+ [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)]
+ public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value)
+ where TPixel : struct, IPixel
+ {
+ provider.RunValidatingProcessorTest(
+ x =>
+ {
+ Size size = x.GetCurrentSize();
+ var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2);
+ x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds);
+ },
+ testOutputDetails: value.ToString(),
+ appendPixelTypeToFileName: false);
+ }
+ }
+}
diff --git a/tests/Images/External b/tests/Images/External
index acc32594c1..36f39bc624 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit acc32594c125656840f8a17e69b0ebb49a370fa6
+Subproject commit 36f39bc624f8a49caf512077bf70cab30c2e5fb4
diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt
index 3afec1c893..a557d16c13 100644
--- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt
+++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt
@@ -63,8 +63,8 @@ Start Offset: 0x00000000
Length = 12772
Identifier = [http://ns.adobe.com/xap/1.0/]
XMP =
- |
- |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28
+ |
+ |Windows Photo Editor 10.0.10011.163842016-01-02T19:22:28
*** Marker: DQT (xFFDB) ***
Define a Quantization Table.
diff --git a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt
index d7c49652e3..3ec02b50d9 100644
--- a/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt
+++ b/tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt
@@ -54,8 +54,8 @@ Start Offset: 0x00000000
Length = 2464
Identifier = [http://ns.adobe.com/xap/1.0/]
XMP =
- |
- |2016-02-28T11:17:08.057
+ |
+ |2016-02-28T11:17:08.057
*** Marker: DQT (xFFDB) ***
Define a Quantization Table.