From 8dd9d392be06cdd9f8b94139eb293a3a7ea0feaa Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 16 Mar 2022 17:15:19 +0100 Subject: [PATCH] Introduce Bounce mode --- .../Convolution/BorderWrappingMode.cs | 10 +- .../Convolution/KernelSamplingMap.cs | 22 ++++ .../Convolution/KernelSamplingMapTest.cs | 117 ++++++++++++++++-- 3 files changed, 136 insertions(+), 13 deletions(-) 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() {