Skip to content

Commit dfa34e9

Browse files
Merge pull request #1113 from SixLabors/sp/bokeh-blur-cache-optimization
Bokeh blur cache optimization
2 parents f975498 + 7c02cc4 commit dfa34e9

File tree

3 files changed

+237
-212
lines changed

3 files changed

+237
-212
lines changed

src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

Lines changed: 7 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Collections.Concurrent;
65
using System.Collections.Generic;
76
using System.Numerics;
87
using System.Runtime.CompilerServices;
@@ -22,26 +21,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
2221
internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
2322
where TPixel : struct, IPixel<TPixel>
2423
{
25-
/// <summary>
26-
/// The kernel radius.
27-
/// </summary>
28-
private readonly int radius;
29-
3024
/// <summary>
3125
/// The gamma highlight factor to use when applying the effect
3226
/// </summary>
3327
private readonly float gamma;
3428

35-
/// <summary>
36-
/// The maximum size of the kernel in either direction
37-
/// </summary>
38-
private readonly int kernelSize;
39-
40-
/// <summary>
41-
/// The number of components to use when applying the bokeh blur
42-
/// </summary>
43-
private readonly int componentsCount;
44-
4529
/// <summary>
4630
/// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W)
4731
/// </summary>
@@ -52,16 +36,6 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
5236
/// </summary>
5337
private readonly Complex64[][] kernels;
5438

55-
/// <summary>
56-
/// The scaling factor for kernel values
57-
/// </summary>
58-
private readonly float kernelsScale;
59-
60-
/// <summary>
61-
/// The mapping of initialized complex kernels and parameters, to speed up the initialization of new <see cref="BokehBlurProcessor{TPixel}"/> instances
62-
/// </summary>
63-
private static readonly ConcurrentDictionary<BokehBlurParameters, BokehBlurKernelData> Cache = new ConcurrentDictionary<BokehBlurParameters, BokehBlurKernelData>();
64-
6539
/// <summary>
6640
/// Initializes a new instance of the <see cref="BokehBlurProcessor{TPixel}"/> class.
6741
/// </summary>
@@ -72,29 +46,16 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
7246
public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
7347
: base(configuration, source, sourceRectangle)
7448
{
75-
this.radius = definition.Radius;
76-
this.kernelSize = (this.radius * 2) + 1;
77-
this.componentsCount = definition.Components;
7849
this.gamma = definition.Gamma;
7950

80-
// Reuse the initialized values from the cache, if possible
81-
var parameters = new BokehBlurParameters(this.radius, this.componentsCount);
82-
if (Cache.TryGetValue(parameters, out BokehBlurKernelData info))
83-
{
84-
this.kernelParameters = info.Parameters;
85-
this.kernelsScale = info.Scale;
86-
this.kernels = info.Kernels;
87-
}
88-
else
89-
{
90-
// Initialize the complex kernels and parameters with the current arguments
91-
(this.kernelParameters, this.kernelsScale) = this.GetParameters();
92-
this.kernels = this.CreateComplexKernels();
93-
this.NormalizeKernels();
51+
// Get the bokeh blur data
52+
BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData(
53+
definition.Radius,
54+
(definition.Radius * 2) + 1,
55+
definition.Components);
9456

95-
// Store them in the cache for future use
96-
Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels));
97-
}
57+
this.kernelParameters = data.Parameters;
58+
this.kernels = data.Kernels;
9859
}
9960

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

110-
/// <summary>
111-
/// Gets the kernel scales to adjust the component values in each kernel
112-
/// </summary>
113-
private static IReadOnlyList<float> KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f };
114-
115-
/// <summary>
116-
/// Gets the available bokeh blur kernel parameters
117-
/// </summary>
118-
private static IReadOnlyList<Vector4[]> KernelComponents { get; } = new[]
119-
{
120-
// 1 component
121-
new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) },
122-
123-
// 2 components
124-
new[]
125-
{
126-
new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f),
127-
new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f)
128-
},
129-
130-
// 3 components
131-
new[]
132-
{
133-
new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f),
134-
new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f),
135-
new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f)
136-
},
137-
138-
// 4 components
139-
new[]
140-
{
141-
new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f),
142-
new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f),
143-
new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f),
144-
new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f)
145-
},
146-
147-
// 5 components
148-
new[]
149-
{
150-
new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f),
151-
new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f),
152-
new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f),
153-
new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f),
154-
new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f)
155-
},
156-
157-
// 6 components
158-
new[]
159-
{
160-
new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f),
161-
new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f),
162-
new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f),
163-
new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f),
164-
new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f),
165-
new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f)
166-
}
167-
};
168-
169-
/// <summary>
170-
/// Gets the kernel parameters and scaling factor for the current count value in the current instance
171-
/// </summary>
172-
private (Vector4[] Parameters, float Scale) GetParameters()
173-
{
174-
// Prepare the kernel components
175-
int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count));
176-
return (KernelComponents[index], KernelScales[index]);
177-
}
178-
179-
/// <summary>
180-
/// Creates the collection of complex 1D kernels with the specified parameters
181-
/// </summary>
182-
private Complex64[][] CreateComplexKernels()
183-
{
184-
var kernels = new Complex64[this.kernelParameters.Length][];
185-
ref Vector4 baseRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
186-
for (int i = 0; i < this.kernelParameters.Length; i++)
187-
{
188-
ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i);
189-
kernels[i] = this.CreateComplex1DKernel(paramsRef.X, paramsRef.Y);
190-
}
191-
192-
return kernels;
193-
}
194-
195-
/// <summary>
196-
/// Creates a complex 1D kernel with the specified parameters
197-
/// </summary>
198-
/// <param name="a">The exponential parameter for each complex component</param>
199-
/// <param name="b">The angle component for each complex component</param>
200-
private Complex64[] CreateComplex1DKernel(float a, float b)
201-
{
202-
var kernel = new Complex64[this.kernelSize];
203-
ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan());
204-
int r = this.radius, n = -r;
205-
206-
for (int i = 0; i < this.kernelSize; i++, n++)
207-
{
208-
// Incrementally compute the range values
209-
float value = n * this.kernelsScale * (1f / r);
210-
value *= value;
211-
212-
// Fill in the complex kernel values
213-
Unsafe.Add(ref baseRef, i) = new Complex64(
214-
MathF.Exp(-a * value) * MathF.Cos(b * value),
215-
MathF.Exp(-a * value) * MathF.Sin(b * value));
216-
}
217-
218-
return kernel;
219-
}
220-
221-
/// <summary>
222-
/// Normalizes the kernels with respect to A * real + B * imaginary
223-
/// </summary>
224-
private void NormalizeKernels()
225-
{
226-
// Calculate the complex weighted sum
227-
float total = 0;
228-
Span<Complex64[]> kernelsSpan = this.kernels.AsSpan();
229-
ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan);
230-
ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan());
231-
232-
for (int i = 0; i < this.kernelParameters.Length; i++)
233-
{
234-
ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i);
235-
int length = kernelRef.Length;
236-
ref Complex64 valueRef = ref kernelRef[0];
237-
ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i);
238-
239-
for (int j = 0; j < length; j++)
240-
{
241-
for (int k = 0; k < length; k++)
242-
{
243-
ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j);
244-
ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k);
245-
total +=
246-
(paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary)))
247-
+ (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real)));
248-
}
249-
}
250-
}
251-
252-
// Normalize the kernels
253-
float scalar = 1f / MathF.Sqrt(total);
254-
for (int i = 0; i < kernelsSpan.Length; i++)
255-
{
256-
ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i);
257-
int length = kernelsRef.Length;
258-
ref Complex64 valueRef = ref kernelsRef[0];
259-
260-
for (int j = 0; j < length; j++)
261-
{
262-
Unsafe.Add(ref valueRef, j) *= scalar;
263-
}
264-
}
265-
}
266-
26771
/// <inheritdoc/>
26872
protected override void OnFrameApply(ImageFrame<TPixel> source)
26973
{

src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.Numerics;
@@ -15,11 +15,6 @@ internal readonly struct BokehBlurKernelData
1515
/// </summary>
1616
public readonly Vector4[] Parameters;
1717

18-
/// <summary>
19-
/// The scaling factor for the kernel values
20-
/// </summary>
21-
public readonly float Scale;
22-
2318
/// <summary>
2419
/// The kernel components to apply the bokeh blur effect
2520
/// </summary>
@@ -29,12 +24,10 @@ internal readonly struct BokehBlurKernelData
2924
/// Initializes a new instance of the <see cref="BokehBlurKernelData"/> struct.
3025
/// </summary>
3126
/// <param name="parameters">The kernel parameters</param>
32-
/// <param name="scale">The kernel scale factor</param>
3327
/// <param name="kernels">The complex kernel components</param>
34-
public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels)
28+
public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels)
3529
{
3630
this.Parameters = parameters;
37-
this.Scale = scale;
3831
this.Kernels = kernels;
3932
}
4033
}

0 commit comments

Comments
 (0)