Skip to content

Commit

Permalink
Merge pull request #1113 from SixLabors/sp/bokeh-blur-cache-optimization
Browse files Browse the repository at this point in the history
Bokeh blur cache optimization
  • Loading branch information
JimBobSquarePants committed Feb 18, 2020
2 parents f975498 + 7c02cc4 commit dfa34e9
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 212 deletions.
Expand Up @@ -2,7 +2,6 @@
// 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;
Expand All @@ -22,26 +21,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The kernel radius.
/// </summary>
private readonly int radius;

/// <summary>
/// The gamma highlight factor to use when applying the effect
/// </summary>
private readonly float gamma;

/// <summary>
/// The maximum size of the kernel in either direction
/// </summary>
private readonly int kernelSize;

/// <summary>
/// The number of components to use when applying the bokeh blur
/// </summary>
private readonly int componentsCount;

/// <summary>
/// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W)
/// </summary>
Expand All @@ -52,16 +36,6 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
/// </summary>
private readonly Complex64[][] kernels;

/// <summary>
/// The scaling factor for kernel values
/// </summary>
private readonly float kernelsScale;

/// <summary>
/// The mapping of initialized complex kernels and parameters, to speed up the initialization of new <see cref="BokehBlurProcessor{TPixel}"/> instances
/// </summary>
private static readonly ConcurrentDictionary<BokehBlurParameters, BokehBlurKernelData> Cache = new ConcurrentDictionary<BokehBlurParameters, BokehBlurKernelData>();

/// <summary>
/// Initializes a new instance of the <see cref="BokehBlurProcessor{TPixel}"/> class.
/// </summary>
Expand All @@ -72,29 +46,16 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.radius = definition.Radius;
this.kernelSize = (this.radius * 2) + 1;
this.componentsCount = definition.Components;
this.gamma = definition.Gamma;

// 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();
// Get the bokeh blur data
BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData(
definition.Radius,
(definition.Radius * 2) + 1,
definition.Components);

// Store them in the cache for future use
Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels));
}
this.kernelParameters = data.Parameters;
this.kernels = data.Kernels;
}

/// <summary>
Expand All @@ -107,163 +68,6 @@ public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor defini
/// </summary>
public IReadOnlyList<Vector4> KernelParameters => this.kernelParameters;

/// <summary>
/// Gets the kernel scales to adjust the component values in each kernel
/// </summary>
private static IReadOnlyList<float> KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f };

/// <summary>
/// Gets the available bokeh blur kernel parameters
/// </summary>
private static IReadOnlyList<Vector4[]> 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)
}
};

/// <summary>
/// Gets the kernel parameters and scaling factor for the current count value in the current instance
/// </summary>
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]);
}

/// <summary>
/// Creates the collection of complex 1D kernels with the specified parameters
/// </summary>
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;
}

/// <summary>
/// Creates a complex 1D kernel with the specified parameters
/// </summary>
/// <param name="a">The exponential parameter for each complex component</param>
/// <param name="b">The angle component for each complex component</param>
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;
}

/// <summary>
/// Normalizes the kernels with respect to A * real + B * imaginary
/// </summary>
private void NormalizeKernels()
{
// Calculate the complex weighted sum
float total = 0;
Span<Complex64[]> 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;
}
}
}

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
Expand Down
@@ -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.Numerics;
Expand All @@ -15,11 +15,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
/// </summary>
public readonly Vector4[] Parameters;

/// <summary>
/// The scaling factor for the kernel values
/// </summary>
public readonly float Scale;

/// <summary>
/// The kernel components to apply the bokeh blur effect
/// </summary>
Expand All @@ -29,12 +24,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
/// Initializes a new instance of the <see cref="BokehBlurKernelData"/> struct.
/// </summary>
/// <param name="parameters">The kernel parameters</param>
/// <param name="scale">The kernel scale factor</param>
/// <param name="kernels">The complex kernel components</param>
public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels)
public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels)
{
this.Parameters = parameters;
this.Scale = scale;
this.Kernels = kernels;
}
}
Expand Down

0 comments on commit dfa34e9

Please sign in to comment.