diff --git a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs index f7a3a6f6de..b0fd0e2cde 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs @@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { private readonly Buffer2D data; + private readonly ResizeKernel[] kernels; + /// /// Initializes a new instance of the class. /// @@ -25,15 +27,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The radius of the kernel public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius) { + this.DestinationSize = destinationSize; int width = (int)Math.Ceiling(kernelRadius * 2); this.data = memoryAllocator.Allocate2D(width, destinationSize, AllocationOptions.Clean); - this.Kernels = new ResizeKernel[destinationSize]; + this.kernels = new ResizeKernel[destinationSize]; } - /// - /// Gets the calculated values. - /// - public ResizeKernel[] Kernels { get; } + public int DestinationSize { get; } /// /// Disposes instance releasing it's backing buffer. @@ -43,6 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.data.Dispose(); } + /// + /// Returns a for an index value between 0 and destinationSize - 1. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + /// /// Computes the weights to apply at each pixel when resizing. /// @@ -88,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float sum = 0; ResizeKernel ws = result.CreateKernel(i, left, right); - result.Kernels[i] = ws; + result.kernels[i] = ws; ref float weightsBaseRef = ref MemoryMarshal.GetReference(ws.GetValues()); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index d0d225d9bf..7c9d39fc55 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -269,9 +269,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int x = minX; x < maxX; x++) { - ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = - window.Convolve(tempRowSpan); + kernel.Convolve(tempRowSpan); } } }); @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int y = rows.Min; y < rows.Max; y++) { // Ensure offsets are normalized for cropping and padding. - ResizeKernel window = this.verticalKernelMap.Kernels[y - startY]; + ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); @@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); // Destination color components - Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn); + Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); } Vector4Utils.UnPremultiply(tempRowSpan); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs new file mode 100644 index 0000000000..932ea54948 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; + +using SixLabors.ImageSharp.Processing.Processors.Transforms; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +{ + public partial class KernelMapTests + { + /// + /// Simplified reference implementation for functionality. + /// + public class ReferenceKernelMap + { + private readonly ReferenceKernel[] kernels; + + public ReferenceKernelMap(ReferenceKernel[] kernels) + { + this.kernels = kernels; + } + + public int DestinationSize => this.kernels.Length; + + public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; + + public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize) + { + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + + if (scale < 1F) + { + scale = 1F; + } + + float radius = MathF.Ceiling(scale * sampler.Radius); + + var result = new List(); + + for (int i = 0; i < destinationSize; i++) + { + float center = ((i + .5F) * ratio) - .5F; + + // Keep inside bounds. + int left = (int)MathF.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } + + int right = (int)MathF.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } + + float sum = 0; + + float[] values = new float[right - left + 1]; + + for (int j = left; j <= right; j++) + { + float weight = sampler.GetValue((j - center) / scale); + sum += weight; + + values[j - left] = weight; + } + + result.Add(new ReferenceKernel(left, values)); + + if (sum > 0) + { + for (int w = 0; w < values.Length; w++) + { + values[w] /= sum; + } + } + } + + return new ReferenceKernelMap(result.ToArray()); + } + } + + public struct ReferenceKernel + { + public ReferenceKernel(int left, float[] values) + { + this.Left = left; + this.Values = values; + } + + public int Left { get; } + + public float[] Values { get; } + + public int Length => this.Values.Length; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs index a7d93ad1d8..9f4d53b964 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs @@ -12,7 +12,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class KernelMapTests + public partial class KernelMapTests { private ITestOutputHelper Output { get; } @@ -30,34 +30,52 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(100, 80, nameof(KnownResamplers.Lanczos8))] [InlineData(100, 10, nameof(KnownResamplers.Lanczos8))] [InlineData(10, 100, nameof(KnownResamplers.Lanczos8))] - public void PrintKernelMap(int srcSize, int destSize, string resamplerName) + public void KernelMapContentIsCorrect(int srcSize, int destSize, string resamplerName) { var resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null); var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); + +#if DEBUG + string text = PrintKernelMap(kernelMap); + this.Output.WriteLine(text); +#endif + + for (int i = 0; i < kernelMap.DestinationSize; i++) + { + ResizeKernel kernel = kernelMap.GetKernel(i); + + ReferenceKernel referenceKernel = referenceMap.GetKernel(i); + + Assert.Equal(referenceKernel.Length, kernel.Length); + Assert.Equal(referenceKernel.Left, kernel.Left); + Assert.True(kernel.GetValues().SequenceEqual(referenceKernel.Values)); + } + } + + private static string PrintKernelMap(KernelMap kernelMap) + { var bld = new StringBuilder(); - foreach (ResizeKernel kernel in kernelMap.Kernels) + for (int i = 0; i < kernelMap.DestinationSize; i++) { + ResizeKernel kernel = kernelMap.GetKernel(i); bld.Append($"({kernel.Left:D3}) || "); Span span = kernel.GetValues(); - for (int i = 0; i < kernel.Length; i++) + + for (int j = 0; j < kernel.Length; j++) { - float value = span[i]; - bld.Append($"{value,7:F5}"); + float value = span[j]; + bld.Append($"{value,8:F5}"); bld.Append(" | "); } bld.AppendLine(); } - string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintKernelMap)); - string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD"; - - File.WriteAllText(fileName, bld.ToString()); - - this.Output.WriteLine(bld.ToString()); + return bld.ToString(); } } } \ No newline at end of file