Browse Source

Merge pull request #2060 from ynse01/convolution-border-wrapping

Convolution: Border wrapping modes
pull/2101/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
6061063fce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
  2. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
  3. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
  4. 25
      src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
  5. 29
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
  6. 36
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  7. 20
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  8. 46
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  9. 36
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  10. 46
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  11. 36
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  12. 152
      src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
  13. 22
      tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs
  14. 421
      tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs

21
src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs

@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle)
=> source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle);
/// <summary>
/// Applies a box 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="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
}
}

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs

@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle)
=> source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
}
}

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs

@ -42,5 +42,26 @@ namespace SixLabors.ImageSharp.Processing
float sigma,
Rectangle rectangle) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
}
}

25
src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Wrapping mode for the border pixels in convolution processing.
/// </summary>
public enum BorderWrappingMode : byte
{
/// <summary>Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh</summary>
Repeat = 0,
/// <summary>Take values from the opposite edge: cdefgh|abcdefgh|abcdefg</summary>
Wrap = 1,
/// <summary>Mirror the last few border values: fedcba|abcdefgh|hgfedcb</summary>
/// <remarks>This Mode is similar to <see cref="Bounce"/>, but here the very border pixel is repeated.</remarks>
Mirror = 2,
/// <summary>Bounce off the border: fedcb|abcdefgh|gfedcb</summary>
/// <remarks>This Mode is similar to <see cref="Mirror"/>, but here the very border pixel is not repeated.</remarks>
Bounce = 3
}
}

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

@ -21,9 +21,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
public BoxBlurProcessor(int radius)
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Initializes a new instance of the <see cref="BoxBlurProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
public BoxBlurProcessor(int radius)
: this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
@ -39,9 +54,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new BoxBlurProcessor<TPixel>(configuration, this, source, sourceRectangle);
=> new BoxBlurProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}

36
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -27,15 +27,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.Kernel = CreateBoxKernel(kernelSize);
}
/// <summary>
/// Initializes a new instance of the <see cref="BoxBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public BoxBlurProcessor(
Configuration configuration,
BoxBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = CreateBoxKernel(kernelSize);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Gets the 1D convolution kernel.
/// </summary>
public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

20
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -26,16 +26,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public Convolution2PassProcessor(
Configuration configuration,
float[] kernel,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
this.Kernel = kernel;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -48,6 +54,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public bool PreserveAlpha { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
@ -63,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest);
mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
// Horizontal convolution
var horizontalOperation = new HorizontalConvolutionRowOperation(

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

@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
: this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
/// </param>
public GaussianBlurProcessor(float sigma, int radius)
: this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Sigma = sigma;
this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new GaussianBlurProcessor<TPixel>(configuration, this, source, sourceRectangle);
=> new GaussianBlurProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}

36
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -30,15 +30,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianBlurProcessor(
Configuration configuration,
GaussianBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Gets the 1D convolution kernel.
/// </summary>
public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

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

@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
: this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
/// </param>
public GaussianSharpenProcessor(float sigma, int radius)
: this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Sigma = sigma;
this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel>
=> new GaussianSharpenProcessor<TPixel>(configuration, this, source, sourceRectangle);
=> new GaussianSharpenProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}

36
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
/// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianSharpenProcessor(
@ -24,10 +24,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
GaussianSharpenProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianSharpenProcessor(
Configuration configuration,
GaussianSharpenProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -35,10 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}

152
src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernel">The convolution kernel.</param>
/// <param name="bounds">The source bounds.</param>
public void BuildSamplingOffsetMap(DenseMatrix<float> kernel, Rectangle bounds)
=> this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds);
=> this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Builds a map of the sampling offsets for the kernel clamped by the given bounds.
@ -40,6 +40,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelWidth">The width (number of columns) of the convolution kernel to use.</param>
/// <param name="bounds">The source bounds.</param>
public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds)
=> this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Builds a map of the sampling offsets for the kernel clamped by the given bounds.
/// </summary>
/// <param name="kernelHeight">The height (number of rows) of the convolution kernel to use.</param>
/// <param name="kernelWidth">The width (number of columns) of the convolution kernel to use.</param>
/// <param name="bounds">The source bounds.</param>
/// <param name="xBorderMode">The wrapping mode on the horizontal borders.</param>
/// <param name="yBorderMode">The wrapping mode on the vertical borders.</param>
public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode)
{
this.yOffsets = this.allocator.Allocate<int>(bounds.Height * kernelHeight);
this.xOffsets = this.allocator.Allocate<int>(bounds.Width * kernelWidth);
@ -49,43 +60,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int minX = bounds.X;
int maxX = bounds.Right - 1;
int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1;
// Calculate the y and x sampling offsets clamped to the given rectangle.
// While this isn't a hotpath we still dip into unsafe to avoid the span bounds
// checks as the can potentially be looping over large arrays.
Span<int> ySpan = this.yOffsets.GetSpan();
ref int ySpanBase = ref MemoryMarshal.GetReference(ySpan);
for (int row = 0; row < bounds.Height; row++)
{
int rowBase = row * kernelHeight;
for (int y = 0; y < kernelHeight; y++)
{
Unsafe.Add(ref ySpanBase, rowBase + y) = row + y + minY - radiusY;
}
}
if (kernelHeight > 1)
{
Numerics.Clamp(ySpan, minY, maxY);
}
Span<int> xSpan = this.xOffsets.GetSpan();
ref int xSpanBase = ref MemoryMarshal.GetReference(xSpan);
for (int column = 0; column < bounds.Width; column++)
{
int columnBase = column * kernelWidth;
for (int x = 0; x < kernelWidth; x++)
{
Unsafe.Add(ref xSpanBase, columnBase + x) = column + x + minX - radiusX;
}
}
if (kernelWidth > 1)
{
Numerics.Clamp(xSpan, minX, maxX);
}
this.BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode);
this.BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -105,5 +81,105 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.isDisposed = true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildOffsets(IMemoryOwner<int> offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode)
{
int radius = kernelSize >> 1;
Span<int> span = offsets.GetSpan();
ref int spanBase = ref MemoryMarshal.GetReference(span);
for (int chunk = 0; chunk < boundsSize; chunk++)
{
int chunkBase = chunk * kernelSize;
for (int i = 0; i < kernelSize; i++)
{
Unsafe.Add(ref spanBase, chunkBase + i) = chunk + i + min - radius;
}
}
this.CorrectBorder(span, kernelSize, min, max, borderMode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CorrectBorder(Span<int> span, int kernelSize, int min, int max, BorderWrappingMode borderMode)
{
var affectedSize = (kernelSize >> 1) * kernelSize;
ref int spanBase = ref MemoryMarshal.GetReference(span);
if (affectedSize > 0)
{
switch (borderMode)
{
case BorderWrappingMode.Repeat:
Numerics.Clamp(span.Slice(0, affectedSize), min, max);
Numerics.Clamp(span.Slice(span.Length - affectedSize), min, max);
break;
case BorderWrappingMode.Mirror:
var min2dec = min + min - 1;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = min2dec - value;
}
}
var max2inc = max + max + 1;
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = max2inc - value;
}
}
break;
case BorderWrappingMode.Bounce:
var min2 = min + min;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = min2 - value;
}
}
var max2 = max + max;
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = max2 - value;
}
}
break;
case BorderWrappingMode.Wrap:
var diff = max - min + 1;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = diff + value;
}
}
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = value - diff;
}
}
break;
}
}
}
}
}

22
tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs

@ -13,13 +13,13 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
public class SwapOrCopyContent
{
private readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator();
private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator();
[Fact]
public void SwapOrCopyContent_WhenBothAllocated()
{
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean))
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean))
using (Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean))
using (Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean))
{
a[1, 3] = 666;
b[1, 3] = 444;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destData = MemoryGroup<int>.Wrap(new int[100]);
using var dest = new Buffer2D<int>(destData, 10, 10);
using (Buffer2D<int> source = this.MemoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean))
using (Buffer2D<int> source = this.memoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean))
{
source[0, 0] = 1;
dest[0, 0] = 2;
@ -68,9 +68,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact]
public void WhenBothAreMemoryOwners_ShouldSwap()
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50;
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(48, 2);
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(50, 2);
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50;
using Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(48, 2);
using Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(50, 2);
Memory<int> a0 = a.FastMemoryGroup[0];
Memory<int> a1 = a.FastMemoryGroup[1];
@ -90,8 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact]
public void WhenBothAreMemoryOwners_ShouldReplaceViews()
{
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(100, 1);
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(100, 2);
using Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(100, 1);
using Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(100, 2);
a.FastMemoryGroup[0].Span[42] = 1;
b.FastMemoryGroup[0].Span[33] = 2;
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destOwner = new TestMemoryManager<Rgba32>(data);
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1);
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(21, 1);
using Buffer2D<Rgba32> source = this.memoryAllocator.Allocate2D<Rgba32>(21, 1);
source.FastMemoryGroup[0].Span[10] = color;
@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destOwner = new TestMemoryManager<Rgba32>(data);
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1);
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(22, 1);
using Buffer2D<Rgba32> source = this.memoryAllocator.Allocate2D<Rgba32>(22, 1);
source.FastMemoryGroup[0].Span[10] = color;

421
tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs

@ -0,0 +1,421 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution
{
[Trait("Category", "Processors")]
public class KernelSamplingMapTest
{
[Fact]
public void KernalSamplingMap_Kernel5Image7x7RepeatBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
0, 0, 0, 1, 2,
0, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 6,
4, 5, 6, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
2, 1, 0, 1, 2,
1, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 5,
4, 5, 6, 5, 4,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7MirrorBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
1, 0, 0, 1, 2,
0, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 6,
4, 5, 6, 6, 5,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7WrapBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
5, 6, 0, 1, 2,
6, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 0,
4, 5, 6, 0, 1,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 1, 2, 3,
2, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 8,
7, 8, 9, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9MirrorBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
2, 1, 1, 2, 3,
1, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 9,
7, 8, 9, 9, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9WrapBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
8, 9, 1, 2, 3,
9, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 1,
7, 8, 9, 1, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7RepeatBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
2, 2, 2, 3, 4,
2, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 8,
6, 7, 8, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
4, 3, 2, 3, 4,
3, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 7,
6, 7, 8, 7, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
3, 2, 2, 3, 4,
2, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 8,
6, 7, 8, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7WrapBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
7, 8, 2, 3, 4,
8, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 2,
6, 7, 8, 2, 3,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7RepeatBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
0, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7BounceBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
1, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 5,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7MirrorBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
0, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7WrapBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
6, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 0,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7RepeatBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
2, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7BounceBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7MirrorBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
2, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7WrapBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
8, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x5WrapBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 5);
var mode = BorderWrappingMode.Wrap;
int[] xExpected =
{
8, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 2,
};
int[] yExpected =
{
6, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, xExpected, yExpected);
}
private void AssertOffsets(Size kernelSize, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode, int[] xExpected, int[] yExpected)
{
// Arrange
var map = new KernelSamplingMap(Configuration.Default.MemoryAllocator);
// Act
map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode);
// Assert
var xOffsets = map.GetColumnOffsetSpan().ToArray();
Assert.Equal(xExpected, xOffsets);
var yOffsets = map.GetRowOffsetSpan().ToArray();
Assert.Equal(yExpected, yOffsets);
}
}
}
Loading…
Cancel
Save