Browse Source

Bokeh blur implementation (#842)

* Added base BokehBlurProcessor class, and kernel parameters

* Added method to calculate the kernel parameters

* Switched to float, added method to create the 1D kernels

* Added complex kernels normalization

* Added BokehBlurExtensions class

* Added the Complex64 struct type

* Switched to Complex64 in the BokehBlurProcessor

* Added caching system for the bokeh processor parameters

* Added WeightedSum method to the Complex64 type

* Added IEquatable<T> interface to the Complex64 type

* New complex types added

* Added method to reshape a DenseMatrix<T> with no copies

* Added bokeh convolution first pass (WIP)

* Added second bokeh convolution pass (WIP)

* Added image sum pass to the bokeh processor (WIP)

* Minor bug fixes (WIP)

* Switched to Vector4 processing in the bokeh computation

* Minor tweaks

* Added Unit test for the bokeh kernel components

* Minor performance improvements

* Minor code refactoring, added gamma parameter (WIP)

* Removed unused temp buffers in the bokeh processing

* Gamma highlight processing implemented

* Speed optimizations, fixed partials computations in target rectangle

* Increased epsilon value in the unit tests

* Fixed for alpha transparency blur

* Fixed a bug when only blurring a target rectangle

* Added bokeh blur image tests (WIP)

* Added IXunitSerializable interface to the test info class

* culture independent parsing in BokehBlurTest.cs

* Performance optimizations in the bokeh processor

* Reduced number of memory allocations, fixed bug with multiple components

* Initialization and other speed improvements

* More initialization speed improvements

* Replaced LINQ with manual loop

* Added BokehBlur overload to just specify the target bounds

* Speed optimizations to the bokeh 1D convolutions

* More speed optimizations to the bokeh processor

* Fixed code style and Complex64.ToString method

* Fixed processing buffer initialization

* Minor performance improvements

* FIxed issue when applying bokeh blur to specific bounds

* Minor speed optimizaations

* Minor code refactoring

* Fixed convolution upper bound in second 1D pass

* improve BokehBlurTest coverage

* use Gray8 instead of Alpha8

* Adjusted guard position in bokeh processor constructor

* Added BokehBlurParameters struct

* Added BokehBlurKernelData struct

* Minor code refactoring

* Fixed API change build errors

* Bug fixes with the pixel premultiplication steps

* Removed unwanted unpremultiplication pass

* Removed unused using directives

* Fixed missing using directives in conditional branches

* Update from latest upstream master

* Update Block8x8F.Generated.cs

* Update GenericBlock8x8.Generated.cs

* Manual checking for files with LF (see gitter)

* Removed unused using directive

* Added IEquatable<ComplexVector4> interface

* Added IEquatable<BokehBlurParameters> interface

* Moved bokeh blur parameters types

* Added reference to original source code

* Complex convolution methods moved to another class

* Switched to MathF in the bokeh blur processor

* Switched to Vector4.Clamp

* Added Buffer2D<T>.Slice API

* Added BokehBlurExecutionMode enum

* Added new bokeh blur processor constructors

* Added new bokeh blur extension overloads with execution mode

* Code refactoring in preparation for the execution mode switch

* Implemented execution mode switch in the bokeh processor

* Moved BokehBlurExecutionMode struct

* Removed unused using directives

* Minor code refactoring

* More minor code refactoring

* Update External

* Fix undisposed buffers

* Bokeh blur processor cache switched to concurrent dictionary

* Minor code refactoring
pull/958/head
Sergio Pedri 7 years ago
committed by James Jackson-South
parent
commit
481481e083
  1. 2
      src/ImageSharp/Color/Color.cs
  2. 105
      src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
  3. 14
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  4. 1
      src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
  5. 3
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  6. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  7. 1
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  8. 1
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  9. 1
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  10. 24
      src/ImageSharp/Memory/Buffer2D{T}.cs
  11. 1
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
  12. 1
      src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs
  13. 1
      src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs
  14. 2
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  15. 2
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs
  16. 2
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs
  17. 95
      src/ImageSharp/Primitives/Complex64.cs
  18. 63
      src/ImageSharp/Primitives/ComplexVector4.cs
  19. 23
      src/ImageSharp/Processing/BokehBlurExecutionMode.cs
  20. 1
      src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs
  21. 1
      src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs
  22. 106
      src/ImageSharp/Processing/Extensions/BokehBlurExtensions.cs
  23. 1
      src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs
  24. 1
      src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs
  25. 1
      src/ImageSharp/Processing/Extensions/FilterExtensions.cs
  26. 1
      src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs
  27. 1
      src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs
  28. 1
      src/ImageSharp/Processing/Extensions/HueExtensions.cs
  29. 1
      src/ImageSharp/Processing/Extensions/InvertExtensions.cs
  30. 1
      src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs
  31. 1
      src/ImageSharp/Processing/Extensions/LomographExtensions.cs
  32. 1
      src/ImageSharp/Processing/Extensions/OpacityExtensions.cs
  33. 1
      src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs
  34. 1
      src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
  35. 2
      src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs
  36. 1
      src/ImageSharp/Processing/Extensions/SaturateExtensions.cs
  37. 1
      src/ImageSharp/Processing/Extensions/SepiaExtensions.cs
  38. 2
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
  39. 2
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  40. 121
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
  41. 585
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  42. 5
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
  43. 1
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  44. 1
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  45. 2
      src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
  46. 43
      src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs
  47. 52
      src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs
  48. 2
      src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs
  49. 3
      src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs
  50. 2
      src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs
  51. 3
      src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs
  52. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
  53. 3
      src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
  54. 2
      src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs
  55. 2
      src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs
  56. 1
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  57. 1
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  58. 1
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
  59. 1
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  60. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
  61. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  62. 1
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  63. 1
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
  64. 2
      src/ImageSharp/Properties/AssemblyInfo.cs
  65. 156
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  66. 2
      tests/Images/External
  67. 4
      tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt
  68. 4
      tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt

2
src/ImageSharp/Color/Color.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

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

@ -0,0 +1,105 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for <see cref="Buffer2D{T}"/>.
/// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement.
/// </summary>
internal static class Buffer2DUtils
{
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="kernel">The 1D convolution kernel.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <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<TPixel>(
Span<Complex64> kernel,
Buffer2D<TPixel> sourcePixels,
Span<ComplexVector4> targetRow,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
ComplexVector4 vector = default;
int kernelLength = kernel.Length;
int radiusY = kernelLength >> 1;
int sourceOffsetColumnBase = column + minColumn;
ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);
for (int i = 0; i < kernelLength; i++)
{
int offsetY = (row + i - radiusY).Clamp(minRow, maxRow);
int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
var currentColor = sourceRowSpan[offsetX].ToVector4();
vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor);
}
targetRow[column] = vector;
}
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// </summary>
/// <param name="kernel">The 1D convolution kernel.</param>
/// <param name="sourceValues">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <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(
Span<Complex64> kernel,
Buffer2D<ComplexVector4> sourceValues,
Span<ComplexVector4> targetRow,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
{
ComplexVector4 vector = default;
int kernelLength = kernel.Length;
int radiusX = kernelLength >> 1;
int sourceOffsetColumnBase = column + minColumn;
int offsetY = row.Clamp(minRow, maxRow);
ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY));
ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel);
for (int x = 0; x < kernelLength; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
vector.Sum(Unsafe.Add(ref baseRef, x) * Unsafe.Add(ref sourceRef, offsetX));
}
targetRow[column] = vector;
}
}
}

14
src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs

@ -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;
@ -45,8 +45,6 @@ namespace SixLabors.ImageSharp
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
Convolve2DImpl(
in matrixY,
in matrixX,
@ -57,7 +55,7 @@ namespace SixLabors.ImageSharp
maxRow,
minColumn,
maxColumn,
ref vector);
out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
@ -95,8 +93,6 @@ namespace SixLabors.ImageSharp
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
Convolve2DImpl(
in matrixY,
in matrixX,
@ -107,7 +103,7 @@ namespace SixLabors.ImageSharp
maxRow,
minColumn,
maxColumn,
ref vector);
out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
Vector4Utils.UnPremultiply(ref vector);
@ -125,7 +121,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn,
ref Vector4 vector)
out Vector4 vector)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vectorY = default;
@ -281,4 +277,4 @@ namespace SixLabors.ImageSharp
}
}
}
}
}

1
src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

3
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -7,9 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp
{
/// <summary>

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.Primitives;

1
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder

1
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components

1
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

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

@ -61,13 +61,31 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
ImageSharp.DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Span;
return ref span[(this.Width * y) + x];
}
}
/// <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.Memory.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>
@ -98,4 +116,4 @@ namespace SixLabors.ImageSharp.Memory
a.Height = bSize.Height;
}
}
}
}

1
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Icc
{

1
src/ImageSharp/PixelFormats/PixelImplementations/Gray16.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;

1
src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs

@ -6,7 +6,6 @@ using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats.Utils;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats
{

2
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -4,8 +4,6 @@
using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;

2
src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs

@ -6,8 +6,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.PixelFormats.Utils
{
/// <summary>

2
src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs

@ -7,8 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.PixelFormats.Utils
{
/// <content>

95
src/ImageSharp/Primitives/Complex64.cs

@ -0,0 +1,95 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// Represents a complex number, where the real and imaginary parts are stored as <see cref="float"/> values.
/// </summary>
/// <remarks>
/// This is a more efficient version of the <see cref="Complex64"/> type.
/// </remarks>
internal readonly struct Complex64 : IEquatable<Complex64>
{
/// <summary>
/// The real part of the complex number
/// </summary>
public readonly float Real;
/// <summary>
/// The imaginary part of the complex number
/// </summary>
public readonly float Imaginary;
/// <summary>
/// Initializes a new instance of the <see cref="Complex64"/> struct.
/// </summary>
/// <param name="real">The real part in the complex number.</param>
/// <param name="imaginary">The imaginary part in the complex number.</param>
public Complex64(float real, float imaginary)
{
this.Real = real;
this.Imaginary = imaginary;
}
/// <summary>
/// Performs the multiplication operation between a <see cref="Complex64"/> intance and a <see cref="float"/> scalar.
/// </summary>
/// <param name="value">The <see cref="Complex64"/> value to multiply.</param>
/// <param name="scalar">The <see cref="float"/> scalar to use to multiply the <see cref="Complex64"/> value.</param>
/// <returns>The <see cref="Complex64"/> result</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Complex64 operator *(Complex64 value, float scalar) => new Complex64(value.Real * scalar, value.Imaginary * scalar);
/// <summary>
/// Performs the multiplication operation between a <see cref="Complex64"/> intance and a <see cref="Vector4"/>.
/// </summary>
/// <param name="value">The <see cref="Complex64"/> value to multiply.</param>
/// <param name="vector">The <see cref="Vector4"/> instance to use to multiply the <see cref="Complex64"/> value.</param>
/// <returns>The <see cref="Complex64"/> result</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ComplexVector4 operator *(Complex64 value, Vector4 vector)
{
return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary };
}
/// <summary>
/// Performs the multiplication operation between a <see cref="Complex64"/> intance and a <see cref="ComplexVector4"/>.
/// </summary>
/// <param name="value">The <see cref="Complex64"/> value to multiply.</param>
/// <param name="vector">The <see cref="ComplexVector4"/> instance to use to multiply the <see cref="Complex64"/> value.</param>
/// <returns>The <see cref="Complex64"/> result</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector)
{
Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary);
Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real);
return new ComplexVector4 { Real = real, Imaginary = imaginary };
}
/// <inheritdoc/>
public bool Equals(Complex64 other)
{
return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary);
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is Complex64 other && this.Equals(other);
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode();
}
}
/// <inheritdoc/>
public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j";
}
}

63
src/ImageSharp/Primitives/ComplexVector4.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Primitives
{
/// <summary>
/// A vector with 4 values of type <see cref="Complex64"/>.
/// </summary>
internal struct ComplexVector4 : IEquatable<ComplexVector4>
{
/// <summary>
/// The real part of the complex vector
/// </summary>
public Vector4 Real;
/// <summary>
/// The imaginary part of the complex number
/// </summary>
public Vector4 Imaginary;
/// <summary>
/// Sums the values in the input <see cref="ComplexVector4"/> to the current instance
/// </summary>
/// <param name="value">The input <see cref="ComplexVector4"/> to sum</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Sum(in ComplexVector4 value)
{
this.Real += value.Real;
this.Imaginary += value.Imaginary;
}
/// <summary>
/// Performs a weighted sum on the current instance according to the given parameters
/// </summary>
/// <param name="a">The 'a' parameter, for the real component</param>
/// <param name="b">The 'b' parameter, for the imaginary component</param>
/// <returns>The resulting <see cref="Vector4"/> value</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b);
/// <inheritdoc/>
public bool Equals(ComplexVector4 other)
{
return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary);
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is ComplexVector4 other && this.Equals(other);
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode();
}
}
}
}

23
src/ImageSharp/Processing/BokehBlurExecutionMode.cs

@ -0,0 +1,23 @@
// 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
}
}

1
src/ImageSharp/Processing/Extensions/AutoOrientExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing

1
src/ImageSharp/Processing/Extensions/BlackWhiteExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

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

@ -0,0 +1,106 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds bokeh blurring extensions to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class BokehBlurExtensions
{
/// <summary>
/// Applies a bokeh blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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>
/// <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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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>
/// <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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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>
/// <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="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, 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);
}
}

1
src/ImageSharp/Processing/Extensions/BoxBlurExtensions.cs

@ -1,7 +1,6 @@
// 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;

1
src/ImageSharp/Processing/Extensions/BrightnessExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/FilterExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/GaussianBlurExtensions.cs

@ -1,7 +1,6 @@
// 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;

1
src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/HueExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/InvertExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/KodachromeExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/LomographExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/OpacityExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/PolaroidExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/ResizeExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;

2
src/ImageSharp/Processing/Extensions/RotateFlipExtensions.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>

1
src/ImageSharp/Processing/Extensions/SaturateExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

1
src/ImageSharp/Processing/Extensions/SepiaExtensions.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;

2
src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;

2
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization

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

@ -0,0 +1,121 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies bokeh blur processing to the image.
/// </summary>
public sealed class BokehBlurProcessor : IImageProcessor
{
/// <summary>
/// The default radius used by the parameterless constructor.
/// </summary>
public const int DefaultRadius = 32;
/// <summary>
/// The default component count used by the parameterless constructor.
/// </summary>
public const int DefaultComponents = 2;
/// <summary>
/// The default gamma used by the parameterless constructor.
/// </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)
{
}
/// <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>
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>
/// Gets the radius.
/// </summary>
public int Radius { get; }
/// <summary>
/// Gets the number of components.
/// </summary>
public int Components { get; }
/// <summary>
/// Gets the gamma highlight factor to use when applying the effect.
/// </summary>
public float Gamma { get; }
/// <summary>
/// Gets the exection mode to use when applying the effect.
/// </summary>
public BokehBlurExecutionMode ExecutionMode { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
return new BokehBlurProcessor<TPixel>(this);
}
}
}

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

@ -0,0 +1,585 @@
// Copyright (c) Six Labors and contributors.
// 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;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies bokeh blur processing to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <remarks>This processor is based on the code from Mike Pound, see <a href="https://github.com/mikepound/convolve">github.com/mikepound/convolve</a>.</remarks>
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 execution mode to use when applying the effect
/// </summary>
private readonly BokehBlurExecutionMode executionMode;
/// <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>
private readonly Vector4[] kernelParameters;
/// <summary>
/// The kernel components for the current instance
/// </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>
/// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param>
public BokehBlurProcessor(BokehBlurProcessor definition)
{
this.radius = definition.Radius;
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);
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();
// Store them in the cache for future use
Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels));
}
}
/// <summary>
/// Gets the complex kernels to use to apply the blur for the current instance
/// </summary>
public IReadOnlyList<Complex64[]> Kernels => this.kernels;
/// <summary>
/// Gets the kernel parameters used to compute the pixel values from each complex pixel
/// </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, Rectangle sourceRectangle, Configuration configuration)
{
// Preliminary gamma highlight pass
this.ApplyGammaExposure(source.PixelBuffer, sourceRectangle, configuration);
// Create a 0-filled buffer to use to store the result of the component convolutions
using (Buffer2D<Vector4> processing = 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 = 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, sourceRectangle, configuration, processing, firstPassBuffer, secondPassBuffer);
}
}
else
{
// Performance priority: allocate two independent buffers and execute both convolutions in parallel mode
using (Buffer2D<ComplexVector4> firstPassValues = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
using (Buffer2D<ComplexVector4> secondPassBuffer = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
{
this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassValues, secondPassBuffer);
}
}
// Apply the inverse gamma exposure pass, and write the final pixel data
this.ApplyInverseGammaExposure(source.PixelBuffer, processing, sourceRectangle, configuration);
}
}
/// <summary>
/// Computes and aggregates the convolution for each complex kernel component in the processor.
/// </summary>
/// <param name="source">The source image. 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 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)
{
// 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++)
{
// 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);
}
}
/// <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="ComplexVector4"/> values to use to store the results.</param>
/// <param name="sourcePixels">The source 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="kernel">The 1D kernel.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyConvolution(
Buffer2D<ComplexVector4> targetValues,
Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle,
Complex64[] kernel,
Configuration configuration)
{
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int maxY = endY - 1;
int maxX = endX - 1;
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<ComplexVector4> targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
for (int x = 0; x < width; x++)
{
Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX);
}
}
});
}
/// <summary>
/// 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="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="kernel">The 1D kernel.</param>
/// <param name="configuration">The <see cref="Configuration"/></param>
private void ApplyConvolution(
Buffer2D<ComplexVector4> targetValues,
Buffer2D<ComplexVector4> sourceValues,
Rectangle sourceRectangle,
Complex64[] kernel,
Configuration configuration)
{
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int maxY = endY - 1;
int maxX = endX - 1;
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,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<ComplexVector4> targetRowSpan = targetValues.GetRowSpan(y).Slice(startX);
for (int x = 0; x < width; x++)
{
Buffer2DUtils.Convolve4(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX);
}
}
});
}
/// <summary>
/// Applies the gamma correction/highlight to the input pixel buffer.
/// </summary>
/// <param name="targetPixels">The target pixel buffer to adjust.</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>
private void ApplyGammaExposure(
Buffer2D<TPixel> targetPixels,
Rectangle sourceRectangle,
Configuration configuration)
{
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;
float exp = this.gamma;
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle,
configuration,
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply);
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int x = 0; x < width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref baseRef, x);
v.X = MathF.Pow(v.X, exp);
v.Y = MathF.Pow(v.Y, exp);
v.Z = MathF.Pow(v.Z, exp);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
}
});
}
/// <summary>
/// Applies the inverse gamma correction/highlight pass, and converts the input <see cref="Vector4"/> buffer into pixel values.
/// </summary>
/// <param name="targetPixels">The target pixels to apply the process to.</param>
/// <param name="sourceValues">The source <see cref="Vector4"/> 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="configuration">The <see cref="Configuration"/></param>
private void ApplyInverseGammaExposure(
Buffer2D<TPixel> targetPixels,
Buffer2D<Vector4> sourceValues,
Rectangle sourceRectangle,
Configuration configuration)
{
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;
float expGamma = 1 / this.gamma;
ParallelHelper.IterateRows(
workingRectangle,
configuration,
rows =>
{
Vector4 low = Vector4.Zero;
var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX);
Span<Vector4> sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX);
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan);
for (int x = 0; x < width; x++)
{
ref Vector4 v = ref Unsafe.Add(ref sourceRef, x);
var clamp = Vector4.Clamp(v, low, high);
v.X = MathF.Pow(clamp.X, expGamma);
v.Y = MathF.Pow(clamp.Y, expGamma);
v.Z = MathF.Pow(clamp.Z, expGamma);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply);
}
});
}
/// <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);
}
}
});
}
}
}

5
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs

@ -1,8 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
@ -47,4 +46,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
return new BoxBlurProcessor<TPixel>(this);
}
}
}
}

1
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{

1
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution

2
src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>

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

@ -0,0 +1,43 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
{
/// <summary>
/// A <see langword="struct"/> that contains data about a set of bokeh blur kernels
/// </summary>
internal readonly struct BokehBlurKernelData
{
/// <summary>
/// The kernel parameters to use for the current set of complex kernels
/// </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>
public readonly Complex64[][] Kernels;
/// <summary>
/// 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)
{
this.Parameters = parameters;
this.Scale = scale;
this.Kernels = kernels;
}
}
}

52
src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters
{
/// <summary>
/// A <see langword="struct"/> that contains parameters to apply a bokeh blur filter
/// </summary>
internal readonly struct BokehBlurParameters : IEquatable<BokehBlurParameters>
{
/// <summary>
/// The size of the convolution kernel to use when applying the bokeh blur
/// </summary>
public readonly int Radius;
/// <summary>
/// The number of complex components to use to approximate the bokeh kernel
/// </summary>
public readonly int Components;
/// <summary>
/// Initializes a new instance of the <see cref="BokehBlurParameters"/> struct.
/// </summary>
/// <param name="radius">The size of the kernel</param>
/// <param name="components">The number of kernel components</param>
public BokehBlurParameters(int radius, int components)
{
this.Radius = radius;
this.Components = components;
}
/// <inheritdoc/>
public bool Equals(BokehBlurParameters other)
{
return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components);
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other);
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode();
}
}
}
}

2
src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>

3
src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>

2
src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>

3
src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>

2
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs

@ -3,8 +3,6 @@
using System;
using SixLabors.ImageSharp.Processing.Processors.Binarization;
namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{
/// <summary>

3
src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
/// <summary>

2
src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
/// <summary>

2
src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
/// <summary>

1
src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization

1
src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization

1
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{

1
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -4,7 +4,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms

4
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -2,12 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;

1
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -3,7 +3,6 @@
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms

1
src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms

2
src/ImageSharp/Properties/AssemblyInfo.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
// Redundant suppressing of SA1413 for Rider.
[assembly:
System.Diagnostics.CodeAnalysis.SuppressMessage(

156
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -0,0 +1,156 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.Primitives;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{
public class BokehBlurTest
{
private static readonly string Components10x2 = @"
[[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j
-0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j
0.01747482+0.04687464j 0.04243676+0.03451751j 0.05564306+0.01742537j
0.06040984+0.00459225j 0.06136251+0.0j 0.06040984+0.00459225j
0.05564306+0.01742537j 0.04243676+0.03451751j 0.01747482+0.04687464j
-0.01428268+0.04224722j -0.03553877+0.0154543j -0.02752798-0.01788438j
0.00387479-0.02682816j 0.02161237-0.00299122j 0.00451261+0.0165137j ]]
[[-0.00227282+0.002851j -0.00152245+0.00604545j 0.00135338+0.00998296j
0.00698622+0.01370844j 0.0153483+0.01605112j 0.02565295+0.01611732j
0.03656958+0.01372368j 0.04662725+0.00954624j 0.05458942+0.00491277j
0.05963937+0.00133843j 0.06136251+0.0j 0.05963937+0.00133843j
0.05458942+0.00491277j 0.04662725+0.00954624j 0.03656958+0.01372368j
0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j
0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]";
[Fact]
public void VerifyComplexComponents()
{
// Get the saved components
var components = new List<Complex64[]>();
foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline))
{
string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
Complex64[] component = values.Select(
value =>
{
Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j");
return new Complex64(
float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture),
float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture));
}).ToArray();
components.Add(component);
}
// Make sure the kernel components are the same
var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma);
var processor = new BokehBlurProcessor<Rgb24>(definition);
Assert.Equal(components.Count, processor.Kernels.Count);
foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b)))
{
Span<Complex64> spanA = a.AsSpan(), spanB = b.AsSpan();
Assert.Equal(spanA.Length, spanB.Length);
for (int i = 0; i < spanA.Length; i++)
{
Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f);
Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f);
}
}
}
public sealed class BokehBlurInfo : IXunitSerializable
{
public int Radius { get; set; }
public int Components { get; set; }
public float Gamma { get; set; }
public void Deserialize(IXunitSerializationInfo info)
{
this.Radius = info.GetValue<int>(nameof(this.Radius));
this.Components = info.GetValue<int>(nameof(this.Components));
this.Gamma = info.GetValue<float>(nameof(this.Gamma));
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(this.Radius), this.Radius, typeof(int));
info.AddValue(nameof(this.Components), this.Components, typeof(int));
info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float));
}
public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}";
}
public static readonly TheoryData<BokehBlurInfo> BokehBlurValues = new TheoryData<BokehBlurInfo>
{
new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 },
new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 },
new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 }
};
public static readonly string[] TestFiles =
{
TestImages.Png.CalliphoraPartial,
TestImages.Png.Bike,
TestImages.Png.BikeGrayscale,
TestImages.Png.Cross,
};
[Theory]
[WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)]
public void BokehBlurFilterProcessor<TPixel>(TestImageProvider<TPixel> provider, BokehBlurInfo value)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x => x.BokehBlur(value.Radius, value.Components, value.Gamma),
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
}
[Theory]
[WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.Gray8)]
public void BokehBlurFilterProcessor_WorksWithAllPixelTypes<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x => x.BokehBlur(8, 2, 3),
appendSourceFileOrDescription: false);
}
[Theory]
[WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)]
public void BokehBlurFilterProcessor_Bounded<TPixel>(TestImageProvider<TPixel> provider, BokehBlurInfo value)
where TPixel : struct, IPixel<TPixel>
{
provider.RunValidatingProcessorTest(
x =>
{
Size size = x.GetCurrentSize();
var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2);
x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds);
},
testOutputDetails: value.ToString(),
appendPixelTypeToFileName: false);
}
}
}

2
tests/Images/External

@ -1 +1 @@
Subproject commit acc32594c125656840f8a17e69b0ebb49a370fa6
Subproject commit 36f39bc624f8a49caf512077bf70cab30c2e5fb4

4
tests/Images/Input/Jpg/baseline/JpegSnoopReports/Floorplan.jpg.txt

@ -63,8 +63,8 @@ Start Offset: 0x00000000
Length = 12772
Identifier = [http://ns.adobe.com/xap/1.0/]
XMP =
|<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreatorTool>Windows Photo Editor 10.0.10011.16384</xmp:CreatorTool><xmp:CreateDate>2016-01-02T19:22:28</xmp:CreateDate></rdf:Description></rdf:RDF></x:xmpmeta>
|<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreatorTool>Windows Photo Editor 10.0.10011.16384</xmp:CreatorTool><xmp:CreateDate>2016-01-02T19:22:28</xmp:CreateDate></rdf:Description></rdf:RDF></x:xmpmeta>
*** Marker: DQT (xFFDB) ***
Define a Quantization Table.

4
tests/Images/Input/Jpg/baseline/JpegSnoopReports/badrst.jpg.txt

@ -54,8 +54,8 @@ Start Offset: 0x00000000
Length = 2464
Identifier = [http://ns.adobe.com/xap/1.0/]
XMP =
|<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreateDate>2016-02-28T11:17:08.057</xmp:CreateDate></rdf:Description></rdf:RDF></x:xmpmeta>
|<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
|<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="uuid:faf5bdd5-ba3d-11da-ad31-d33d75182f1b" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreateDate>2016-02-28T11:17:08.057</xmp:CreateDate></rdf:Description></rdf:RDF></x:xmpmeta>
*** Marker: DQT (xFFDB) ***
Define a Quantization Table.

Loading…
Cancel
Save