Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bokeh blur cache optimization #1113

Merged
merged 5 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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