From 9ef5c204c04638b8814566121cb7ab1bd266f1f9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Feb 2019 15:03:51 +0100 Subject: [PATCH 1/9] Added base BokehBlurProcessor class, and kernel parameters --- .../Convolution/BokehBlurProcessor.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs new file mode 100644 index 0000000000..47c61b8cc5 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +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; + + /// + /// Initializes a new instance of the class. + /// + public BokehBlurProcessor() + { + } + + /// + /// Gets the Radius + /// + public int Radius { get; } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + /// Gets the kernel scales to adjust the component values in each kernel + /// + private static IReadOnlyList KernelScales { get; } = new[] { 1.4, 1.2, 1.2, 1.2, 1.2, 1.2 }; + + /// + /// Gets the available bokeh blur kernel parameters + /// + private static IReadOnlyList KernelParameters { get; } = new[] + { + // 1 component + new[,] { { 0.862325, 1.624835, 0.767583, 1.862321 } }, + + // 2 components + new[,] + { + { 0.886528, 5.268909, 0.411259, -0.548794 }, + { 1.960518, 1.558213, 0.513282, 4.56111 } + }, + + // 3 components + new[,] + { + { 2.17649, 5.043495, 1.621035, -2.105439 }, + { 1.019306, 9.027613, -0.28086, -0.162882 }, + { 2.81511, 1.597273, -0.366471, 10.300301 } + }, + + // 4 components + new[,] + { + { 4.338459, 1.553635, -5.767909, 46.164397 }, + { 3.839993, 4.693183, 9.795391, -15.227561 }, + { 2.791880, 8.178137, -3.048324, 0.302959 }, + { 1.342190, 12.328289, 0.010001, 0.244650 } + }, + + // 5 components + new[,] + { + { 4.892608, 1.685979, -22.356787, 85.91246 }, + { 4.71187, 4.998496, 35.918936, -28.875618 }, + { 4.052795, 8.244168, -13.212253, -1.578428 }, + { 2.929212, 11.900859, 0.507991, 1.816328 }, + { 1.512961, 16.116382, 0.138051, -0.01 } + }, + + // 6 components + new[,] + { + { 5.143778, 2.079813, -82.326596, 111.231024 }, + { 5.612426, 6.153387, 113.878661, 58.004879 }, + { 5.982921, 9.802895, 39.479083, -162.028887 }, + { 6.505167, 11.059237, -71.286026, 95.027069 }, + { 3.869579, 14.81052, 1.405746, -3.704914 }, + { 2.201904, 19.032909, -0.152784, -0.107988 } + } + }; + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => throw new NotImplementedException(); + } +} From 57feddc8ce1d7b2f3b26796db998901007a833d6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Feb 2019 15:25:31 +0100 Subject: [PATCH 2/9] Added method to calculate the kernel parameters --- .../Convolution/BokehBlurProcessor.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 47c61b8cc5..6eddb5ef20 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -21,11 +21,17 @@ internal class BokehBlurProcessor : ImageProcessor /// private readonly int kernelSize; + /// + /// The number of components to use when applying the bokeh blur + /// + private readonly int componentsCount; + /// /// Initializes a new instance of the class. /// - public BokehBlurProcessor() + public BokehBlurProcessor(int components = 2) { + this.componentsCount = components; } /// @@ -102,6 +108,30 @@ public BokehBlurProcessor() } }; + /// + /// Gets the kernel parameters and scaling factor for the current count value in the current instance + /// + private (IReadOnlyList> Components, double Scale) GetParameters() + { + // Prepare the kernel components + int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelParameters.Count)); + double[,] parameters = KernelParameters[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]); + } + /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => throw new NotImplementedException(); } From e649369f97539b04ba159aea94983993b0486377 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Feb 2019 15:59:47 +0100 Subject: [PATCH 3/9] Switched to float, added method to create the 1D kernels --- .../Convolution/BokehBlurProcessor.cs | 125 ++++++++++++------ 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 6eddb5ef20..512985548a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Primitives; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -27,99 +28,117 @@ internal class BokehBlurProcessor : ImageProcessor private readonly int componentsCount; /// - /// Initializes a new instance of the class. + /// The kernel components to use for the current instance /// - public BokehBlurProcessor(int components = 2) - { - this.componentsCount = components; - } + private readonly IReadOnlyList> kernelComponents; /// - /// Gets the Radius + /// The scaling factor for kernel values /// - public int Radius { get; } + private readonly float kernelsScale; /// - /// Gets the horizontal gradient operator. + /// The complex kernels to use to apply the blur for the current instance /// - public DenseMatrix KernelX { get; } + private readonly IReadOnlyList complexKernels; + + /// + /// 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; + + (this.kernelComponents, this.kernelsScale) = this.GetParameters(); + this.complexKernels = ( + from component in this.kernelComponents + select this.CreateComplex1DKernel(component['a'], component['b'])).ToArray(); + } /// - /// Gets the vertical gradient operator. + /// Gets the Radius /// - public DenseMatrix KernelY { get; } + public int Radius { get; } /// /// Gets the kernel scales to adjust the component values in each kernel /// - private static IReadOnlyList KernelScales { get; } = new[] { 1.4, 1.2, 1.2, 1.2, 1.2, 1.2 }; + 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 KernelParameters { get; } = new[] + private static IReadOnlyList KernelParameters { get; } = new[] { // 1 component - new[,] { { 0.862325, 1.624835, 0.767583, 1.862321 } }, + new[,] { { 0.862325f, 1.624835f, 0.767583f, 1.862321f } }, // 2 components new[,] { - { 0.886528, 5.268909, 0.411259, -0.548794 }, - { 1.960518, 1.558213, 0.513282, 4.56111 } + { 0.886528f, 5.268909f, 0.411259f, -0.548794f }, + { 1.960518f, 1.558213f, 0.513282f, 4.56111f } }, // 3 components new[,] { - { 2.17649, 5.043495, 1.621035, -2.105439 }, - { 1.019306, 9.027613, -0.28086, -0.162882 }, - { 2.81511, 1.597273, -0.366471, 10.300301 } + { 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.338459, 1.553635, -5.767909, 46.164397 }, - { 3.839993, 4.693183, 9.795391, -15.227561 }, - { 2.791880, 8.178137, -3.048324, 0.302959 }, - { 1.342190, 12.328289, 0.010001, 0.244650 } + { 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.892608, 1.685979, -22.356787, 85.91246 }, - { 4.71187, 4.998496, 35.918936, -28.875618 }, - { 4.052795, 8.244168, -13.212253, -1.578428 }, - { 2.929212, 11.900859, 0.507991, 1.816328 }, - { 1.512961, 16.116382, 0.138051, -0.01 } + { 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.143778, 2.079813, -82.326596, 111.231024 }, - { 5.612426, 6.153387, 113.878661, 58.004879 }, - { 5.982921, 9.802895, 39.479083, -162.028887 }, - { 6.505167, 11.059237, -71.286026, 95.027069 }, - { 3.869579, 14.81052, 1.405746, -3.704914 }, - { 2.201904, 19.032909, -0.152784, -0.107988 } + { 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> Components, double Scale) GetParameters() + private (IReadOnlyList> Components, float Scale) GetParameters() { // Prepare the kernel components int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelParameters.Count)); - double[,] parameters = KernelParameters[index]; - var mapping = new IReadOnlyDictionary[parameters.GetLength(0)]; + float[,] parameters = KernelParameters[index]; + var mapping = new IReadOnlyDictionary[parameters.GetLength(0)]; for (int i = 0; i < parameters.GetLength(0); i++) { - mapping[i] = new Dictionary + mapping[i] = new Dictionary { ['a'] = parameters[i, 0], ['b'] = parameters[i, 1], @@ -132,6 +151,34 @@ public BokehBlurProcessor(int components = 2) 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 Complex[] 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 Complex[this.kernelSize]; + for (int i = 0; i < this.kernelSize; i++) + { + double + real = Math.Exp(-a * ax[i]) * Math.Cos(b * ax[i]), + imaginary = Math.Exp(-a * ax[i]) * Math.Sin(b * ax[i]); + kernel[i] = new Complex(real, imaginary); + } + + return kernel; + } + /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => throw new NotImplementedException(); } From f0195252e89f9bcd1258468dd528c0f324835300 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Feb 2019 16:20:54 +0100 Subject: [PATCH 4/9] Added complex kernels normalization --- .../Convolution/BokehBlurProcessor.cs | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 512985548a..344d2717b8 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -30,7 +30,7 @@ internal class BokehBlurProcessor : ImageProcessor /// /// The kernel components to use for the current instance /// - private readonly IReadOnlyList> kernelComponents; + private readonly IReadOnlyList> kernelParameters; /// /// The scaling factor for kernel values @@ -57,10 +57,11 @@ public BokehBlurProcessor(int radius = 32, int components = 2) this.kernelSize = (radius * 2) + 1; this.componentsCount = components; - (this.kernelComponents, this.kernelsScale) = this.GetParameters(); + (this.kernelParameters, this.kernelsScale) = this.GetParameters(); this.complexKernels = ( - from component in this.kernelComponents + from component in this.kernelParameters select this.CreateComplex1DKernel(component['a'], component['b'])).ToArray(); + this.NormalizeKernels(); } /// @@ -76,7 +77,7 @@ from component in this.kernelComponents /// /// Gets the available bokeh blur kernel parameters /// - private static IReadOnlyList KernelParameters { get; } = new[] + private static IReadOnlyList KernelComponents { get; } = new[] { // 1 component new[,] { { 0.862325f, 1.624835f, 0.767583f, 1.862321f } }, @@ -130,11 +131,11 @@ from component in this.kernelComponents /// /// Gets the kernel parameters and scaling factor for the current count value in the current instance /// - private (IReadOnlyList> Components, float Scale) GetParameters() + private (IReadOnlyList> Parameters, float Scale) GetParameters() { // Prepare the kernel components - int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelParameters.Count)); - float[,] parameters = KernelParameters[index]; + 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++) { @@ -179,6 +180,37 @@ private Complex[] CreateComplex1DKernel(float a, float b) return kernel; } + /// + /// Normalizes the kernels with respect to A * real + B * imaginary + /// + private void NormalizeKernels() + { + // Calculate the complex weighted sum + double total = 0; + foreach ((Complex[] 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 (Complex[] 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(); } From db199610ec699d2c884ab011466d2a2573c9afa1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 19 Feb 2019 16:31:50 +0100 Subject: [PATCH 5/9] Added BokehBlurExtensions class --- .../Processing/BokehBlurExtensions.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/ImageSharp/Processing/BokehBlurExtensions.cs 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); + } +} From be6f1f211f4c3a82ec081cf67fc0dd4d0a7aa2dd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Feb 2019 12:46:32 +0100 Subject: [PATCH 6/9] Added the Complex64 struct type --- src/ImageSharp/Primitives/Complex64.cs | 51 ++++++++++++++++++++++++++ src/ImageSharp/Primitives/Rational.cs | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Primitives/Complex64.cs diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs new file mode 100644 index 0000000000..02469d2dea --- /dev/null +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +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 + 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 + public static Complex64 operator +(Complex64 left, Complex64 right) => new Complex64(left.Real + right.Real, left.Imaginary + right.Imaginary); + } +} 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) { From 87e0dd82af2e150d7e9ebf394c3734e8449373b6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Feb 2019 12:49:48 +0100 Subject: [PATCH 7/9] Switched to Complex64 in the BokehBlurProcessor --- .../Convolution/BokehBlurProcessor.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 344d2717b8..7aa298e9c0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -40,7 +40,7 @@ internal class BokehBlurProcessor : ImageProcessor /// /// The complex kernels to use to apply the blur for the current instance /// - private readonly IReadOnlyList complexKernels; + private readonly IReadOnlyList complexKernels; /// /// Initializes a new instance of the class. @@ -157,7 +157,7 @@ from component in this.kernelParameters /// /// The exponential parameter for each complex component /// The angle component for each complex component - private Complex[] CreateComplex1DKernel(float a, float b) + private Complex64[] CreateComplex1DKernel(float a, float b) { // Precompute the range values float[] ax = Enumerable.Range(-this.Radius, this.Radius + 1).Select( @@ -168,13 +168,13 @@ private Complex[] CreateComplex1DKernel(float a, float b) }).ToArray(); // Compute the complex kernels - var kernel = new Complex[this.kernelSize]; + var kernel = new Complex64[this.kernelSize]; for (int i = 0; i < this.kernelSize; i++) { - double - real = Math.Exp(-a * ax[i]) * Math.Cos(b * ax[i]), - imaginary = Math.Exp(-a * ax[i]) * Math.Sin(b * ax[i]); - kernel[i] = new Complex(real, imaginary); + 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; @@ -187,7 +187,7 @@ private void NormalizeKernels() { // Calculate the complex weighted sum double total = 0; - foreach ((Complex[] kernel, IReadOnlyDictionary param) in this.complexKernels.Zip(this.kernelParameters, (k, p) => (k, p))) + foreach ((Complex64[] kernel, IReadOnlyDictionary param) in this.complexKernels.Zip(this.kernelParameters, (k, p) => (k, p))) { for (int i = 0; i < kernel.Length; i++) { @@ -202,7 +202,7 @@ private void NormalizeKernels() // Normalize the kernels float scalar = (float)(1f / Math.Sqrt(total)); - foreach (Complex[] kernel in this.complexKernels) + foreach (Complex64[] kernel in this.complexKernels) { for (int i = 0; i < kernel.Length; i++) { From 062b4e3c0b304a0283d61320ef9f7c4d281f700a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Feb 2019 13:00:35 +0100 Subject: [PATCH 8/9] Added caching system for the bokeh processor parameters --- .../Convolution/BokehBlurProcessor.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 7aa298e9c0..befc9eeec2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -42,6 +42,12 @@ internal class BokehBlurProcessor : ImageProcessor /// 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. /// @@ -57,11 +63,25 @@ public BokehBlurProcessor(int radius = 32, int components = 2) this.kernelSize = (radius * 2) + 1; this.componentsCount = components; - (this.kernelParameters, this.kernelsScale) = this.GetParameters(); - this.complexKernels = ( - from component in this.kernelParameters - select this.CreateComplex1DKernel(component['a'], component['b'])).ToArray(); - this.NormalizeKernels(); + // 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)); + } } /// From c150df95e828892be3ea91f39fc06415887b6e79 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 20 Feb 2019 13:20:20 +0100 Subject: [PATCH 9/9] Added WeightedSum method to the Complex64 type --- src/ImageSharp/Primitives/Complex64.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs index 02469d2dea..6219380f70 100644 --- a/src/ImageSharp/Primitives/Complex64.cs +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Primitives { /// @@ -38,6 +40,7 @@ public Complex64(float real, float imaginary) /// 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); /// @@ -46,6 +49,16 @@ public Complex64(float real, float imaginary) /// 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); } }