Browse Source

Merge pull request #1058 from Sergio0694/improvement_bokeh-blur-speedup-and-memory-reduction

Bokeh blur speedup and memory usage improvement
pull/1059/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
0322ee2e6d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
  2. 17
      src/ImageSharp/Memory/Buffer2D{T}.cs
  3. 23
      src/ImageSharp/Processing/BokehBlurExecutionMode.cs
  4. 48
      src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs
  5. 44
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
  6. 136
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

15
src/ImageSharp/Common/Helpers/Buffer2DUtils.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -62,7 +63,7 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values and accumulates the partial results.
/// </summary>
/// <param name="kernel">The 1D convolution kernel.</param>
/// <param name="sourceValues">The source frame.</param>
@ -73,16 +74,20 @@ namespace SixLabors.ImageSharp
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
public static void Convolve4(
/// <param name="z">The weight factor for the real component of the complex pixel values.</param>
/// <param name="w">The weight factor for the imaginary component of the complex pixel values.</param>
public static void Convolve4AndAccumulatePartials(
Span<Complex64> kernel,
Buffer2D<ComplexVector4> sourceValues,
Span<ComplexVector4> targetRow,
Span<Vector4> targetRow,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
int maxColumn,
float z,
float w)
{
ComplexVector4 vector = default;
int kernelLength = kernel.Length;
@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp
vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX));
}
targetRow[column] = vector;
targetRow[column] += vector.WeightedSum(z, w);
}
}
}

17
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -69,23 +69,6 @@ namespace SixLabors.ImageSharp.Memory
}
}
/// <summary>
/// Creates a new <see cref="Buffer2D{T}"/> instance that maps to a target rows interval from the current instance.
/// </summary>
/// <param name="y">The target vertical offset for the rows interval to retrieve.</param>
/// <param name="h">The desired number of rows to extract.</param>
/// <returns>The new <see cref="Buffer2D{T}"/> instance with the requested rows interval.</returns>
public Buffer2D<T> 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<T> slice = this.GetMemory().Slice(y * this.Width, h * this.Width);
var memory = new MemorySource<T>(slice);
return new Buffer2D<T>(memory, this.Width, h);
}
/// <summary>
/// Disposes the <see cref="Buffer2D{T}"/> instance
/// </summary>

23
src/ImageSharp/Processing/BokehBlurExecutionMode.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// An <see langword="enum"/> that indicates execution options for the <see cref="BokehBlurProcessor"/>.
/// </summary>
public enum BokehBlurExecutionMode
{
/// <summary>
/// Indicates that the maximum performance should be prioritized over memory usage.
/// </summary>
PreferMaximumPerformance,
/// <summary>
/// Indicates that the memory usage should be prioritized over raw performance.
/// </summary>
PreferLowMemoryUsage
}
}

48
src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs

@ -19,15 +19,6 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source)
=> source.ApplyProcessor(new BokehBlurProcessor());
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="executionMode">The execution mode to use when applying the processor.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, BokehBlurExecutionMode executionMode)
=> source.ApplyProcessor(new BokehBlurProcessor(executionMode));
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
@ -39,18 +30,6 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma)
=> source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma));
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <param name="executionMode">The execution mode to use when applying the processor.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, BokehBlurExecutionMode executionMode)
=> source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma, executionMode));
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
@ -62,18 +41,6 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle)
=> source.ApplyProcessor(new BokehBlurProcessor(), rectangle);
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="executionMode">The execution mode to use when applying the processor.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, Rectangle rectangle, BokehBlurExecutionMode executionMode)
=> source.ApplyProcessor(new BokehBlurProcessor(executionMode), rectangle);
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
@ -87,20 +54,5 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle)
=> source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle);
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="components">The 'components' value representing the number of kernels to use to approximate the bokeh effect.</param>
/// <param name="gamma">The gamma highlight factor to use to emphasize bright spots in the source image</param>
/// <param name="executionMode">The execution mode to use when applying the processor.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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);
}
}

44
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs

@ -26,27 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public const float DefaultGamma = 3F;
/// <summary>
/// The default execution mode used by the parameterless constructor.
/// </summary>
public const BokehBlurExecutionMode DefaultExecutionMode = BokehBlurExecutionMode.PreferLowMemoryUsage;
/// <summary>
/// Initializes a new instance of the <see cref="BokehBlurProcessor"/> class.
/// </summary>
public BokehBlurProcessor()
: this(DefaultRadius, DefaultComponents, DefaultGamma, DefaultExecutionMode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BokehBlurProcessor"/> class.
/// </summary>
/// <param name="executionMode">
/// The execution mode to use when applying the processor.
/// </param>
public BokehBlurProcessor(BokehBlurExecutionMode executionMode)
: this(DefaultRadius, DefaultComponents, DefaultGamma, executionMode)
: this(DefaultRadius, DefaultComponents, DefaultGamma)
{
}
@ -63,33 +47,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The gamma highlight factor to use to further process the image.
/// </param>
public BokehBlurProcessor(int radius, int components, float gamma)
: this(radius, components, gamma, DefaultExecutionMode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BokehBlurProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
/// <param name="components">
/// The number of components to use to approximate the original 2D bokeh blur convolution kernel.
/// </param>
/// <param name="gamma">
/// The gamma highlight factor to use to further process the image.
/// </param>
/// <param name="executionMode">
/// The execution mode to use when applying the processor.
/// </param>
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;
}
/// <summary>
@ -107,11 +70,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public float Gamma { get; }
/// <summary>
/// Gets the execution mode to use when applying the effect.
/// </summary>
public BokehBlurExecutionMode ExecutionMode { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>

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

@ -36,11 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
private readonly float gamma;
/// <summary>
/// The execution mode to use when applying the effect
/// </summary>
private readonly BokehBlurExecutionMode executionMode;
/// <summary>
/// The maximum size of the kernel in either direction
/// </summary>
@ -84,7 +79,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
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);
@ -280,30 +274,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration);
// Create a 0-filled buffer to use to store the result of the component convolutions
using (Buffer2D<Vector4> processing = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean))
using (Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(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<ComplexVector4> buffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Width, source.Height + this.radius))
using (Buffer2D<ComplexVector4> firstPassBuffer = buffer.Slice(this.radius, source.Height))
using (Buffer2D<ComplexVector4> secondPassBuffer = buffer.Slice(0, source.Height))
{
this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassBuffer, secondPassBuffer);
}
}
else
{
// Performance priority: allocate two independent buffers and execute both convolutions in parallel mode
using (Buffer2D<ComplexVector4> firstPassValues = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
using (Buffer2D<ComplexVector4> secondPassBuffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
{
this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassValues, secondPassBuffer);
}
}
// Perform the 1D convolutions on all the kernel components and accumulate the results
this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer);
// Apply the inverse gamma exposure pass, and write the final pixel data
this.ApplyInverseGammaExposure(source.PixelBuffer, processing, this.SourceRectangle, this.Configuration);
this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration);
}
}
@ -314,29 +291,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="processingBuffer">The buffer with the raw pixel data to use to aggregate the results of each convolution.</param>
/// <param name="firstPassBuffer">The complex buffer to use for the first 1D convolution pass for each kernel.</param>
/// <param name="secondPassBuffer">The complex buffer to use for the second 1D convolution pass for each kernel.</param>
private void OnFrameApplyCore(
ImageFrame<TPixel> source,
Rectangle sourceRectangle,
Configuration configuration,
Buffer2D<Vector4> processingBuffer,
Buffer2D<ComplexVector4> firstPassBuffer,
Buffer2D<ComplexVector4> secondPassBuffer)
Buffer2D<Vector4> processingBuffer)
{
// 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++)
using (Buffer2D<ComplexVector4> firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
{
// 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);
// Perform two 1D convolutions for each component in the current instance
ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan());
ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.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);
Vector4 parameters = Unsafe.Add(ref paramsRef, i);
// Compute the two 1D convolutions and accumulate the partial results on the target buffer
this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration);
this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W);
}
}
}
@ -389,19 +365,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Applies the process to the specified portion of the specified <see cref="Buffer2D{T}"/> buffer at the specified location
/// and with the specified size.
/// </summary>
/// <param name="targetValues">The target <see cref="ComplexVector4"/> values to use to store the results.</param>
/// <param name="targetValues">The target <see cref="Vector4"/> values to use to store the results.</param>
/// <param name="sourceValues">The source complex values. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="sourceRectangle">The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.</param>
/// <param name="kernel">The 1D kernel.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="z">The weight factor for the real component of the complex pixel values.</param>
/// <param name="w">The weight factor for the imaginary component of the complex pixel values.</param>
private void ApplyConvolution(
Buffer2D<ComplexVector4> targetValues,
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> sourceValues,
Rectangle sourceRectangle,
Complex64[] kernel,
Configuration configuration)
Configuration configuration,
float z,
float w)
{
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
@ -413,12 +391,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
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,
@ -426,11 +398,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<ComplexVector4> targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
Span<Vector4> targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
for (int x = 0; x < width; x++)
{
Buffer2DUtils.Convolve4(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX);
Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w);
}
}
});
@ -536,53 +508,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
});
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="targetValues">The target <see cref="Buffer2D{T}"/> instance to use to store the results.</param>
/// <param name="sourceValues">The source complex pixels. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="z">The weight factor for the real component of the complex pixel values.</param>
/// <param name="w">The weight factor for the imaginary component of the complex pixel values.</param>
private void SumProcessingPartials(
Buffer2D<Vector4> targetValues,
Buffer2D<ComplexVector4> 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<Vector4> targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
Span<ComplexVector4> 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);
}
}
});
}
}
}

Loading…
Cancel
Save