diff --git a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
index 1fd1020cc..e835fc748 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
@@ -14,8 +14,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Take values from the opposite edge: cdefgh|abcdefgh|abcdefg
Wrap = 1,
- /// Mirror the last few border values: fedcb|abcdefgh|gfedcb
- /// Please note this mode does not repeat the very border pixel, as this gives better image quality.
- Mirror = 2
+ /// Mirror the last few border values: fedcba|abcdefgh|hgfedcb
+ /// This Mode is similar to , but here the very border pixel is repeated.
+ Mirror = 2,
+
+ /// Bounce off the border: fedcb|abcdefgh|gfedcb
+ /// This Mode is similar to , but here the very border pixel is not repeated.
+ Bounce = 3
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
index fde8bae3c..98a4ca357 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
@@ -114,6 +114,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
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++)
{
diff --git a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs
index 68af58b59..64e1ea2cb 100644
--- a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs
@@ -29,11 +29,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
}
[Fact]
- public void KernalSamplingMap_Kernel5Image7x7MirrorBorder()
+ public void KernalSamplingMap_Kernel5Image7x7BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
- var mode = BorderWrappingMode.Mirror;
+ var mode = BorderWrappingMode.Bounce;
int[] expected =
{
2, 1, 0, 1, 2,
@@ -47,6 +47,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
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()
{
@@ -67,11 +86,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
}
[Fact]
- public void KernalSamplingMap_Kernel5Image9x9MirrorBorder()
+ public void KernalSamplingMap_Kernel5Image9x9BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
- var mode = BorderWrappingMode.Mirror;
+ var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 1, 2, 3,
@@ -87,6 +106,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
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()
{
@@ -128,11 +168,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
}
[Fact]
- public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile()
+ public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
- var mode = BorderWrappingMode.Mirror;
+ var mode = BorderWrappingMode.Bounce;
int[] expected =
{
4, 3, 2, 3, 4,
@@ -146,6 +186,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
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()
{
@@ -185,11 +244,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
}
[Fact]
- public void KernalSamplingMap_Kernel3Image7x7MirrorBorder()
+ public void KernalSamplingMap_Kernel3Image7x7BounceBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
- var mode = BorderWrappingMode.Mirror;
+ var mode = BorderWrappingMode.Bounce;
int[] expected =
{
1, 0, 1,
@@ -203,6 +262,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
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()
{
@@ -242,11 +320,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
}
[Fact]
- public void KernalSamplingMap_Kernel3Image7MirrorBorderTile()
+ public void KernalSamplingMap_Kernel3Image7BounceBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
- var mode = BorderWrappingMode.Mirror;
+ var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 3,
@@ -260,6 +338,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
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()
{