diff --git a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
new file mode 100644
index 0000000000..277be53fff
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Holds the values in an optimized contigous memory region.
+ ///
+ internal class KernelMap : IDisposable
+ {
+ private readonly Buffer2D data;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use for allocations.
+ /// The size of the destination window
+ /// The radius of the kernel
+ public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius)
+ {
+ int width = (int)Math.Ceiling(kernelRadius * 2);
+ this.data = memoryAllocator.Allocate2D(width, destinationSize, AllocationOptions.Clean);
+ this.Kernels = new ResizeKernel[destinationSize];
+ }
+
+ ///
+ /// Gets the calculated values.
+ ///
+ public ResizeKernel[] Kernels { get; }
+
+ ///
+ /// Disposes instance releasing it's backing buffer.
+ ///
+ public void Dispose()
+ {
+ this.data.Dispose();
+ }
+
+ ///
+ /// Computes the weights to apply at each pixel when resizing.
+ ///
+ /// The
+ /// The destination size
+ /// The source size
+ /// The to use for buffer allocations
+ /// The
+ public static KernelMap Calculate(
+ IResampler sampler,
+ int destinationSize,
+ int sourceSize,
+ MemoryAllocator memoryAllocator)
+ {
+ float ratio = (float)sourceSize / destinationSize;
+ float scale = ratio;
+
+ if (scale < 1F)
+ {
+ scale = 1F;
+ }
+
+ float radius = MathF.Ceiling(scale * sampler.Radius);
+ var result = new KernelMap(memoryAllocator, destinationSize, radius);
+
+ 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;
+
+ ResizeKernel ws = result.CreateKernel(i, left, right);
+ result.Kernels[i] = ws;
+
+ ref float weightsBaseRef = ref ws.GetStartReference();
+
+ for (int j = left; j <= right; j++)
+ {
+ float weight = sampler.GetValue((j - center) / scale);
+ sum += weight;
+
+ // weights[j - left] = weight:
+ Unsafe.Add(ref weightsBaseRef, j - left) = weight;
+ }
+
+ // Normalize, best to do it here rather than in the pixel loop later on.
+ if (sum > 0)
+ {
+ for (int w = 0; w < ws.Length; w++)
+ {
+ // weights[w] = weights[w] / sum:
+ ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
+ wRef /= sum;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Slices a weights value at the given positions.
+ ///
+ /// The index in destination buffer
+ /// The local left index value
+ /// The local right index value
+ /// The weights
+ private ResizeKernel CreateKernel(int destIdx, int leftIdx, int rightIdx)
+ {
+ return new ResizeKernel(destIdx, leftIdx, this.data, rightIdx - leftIdx + 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
similarity index 86%
rename from src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
rename to src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
index 56c665c30d..f149523fc7 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
@@ -13,9 +13,9 @@ using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Points to a collection of of weights allocated in .
+ /// Points to a collection of of weights allocated in .
///
- internal struct WeightsWindow
+ internal struct ResizeKernel
{
///
/// The local left index position
@@ -38,14 +38,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly Memory buffer;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
/// The destination index in the buffer
/// The local left index
/// The span
/// The length of the window
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal WeightsWindow(int index, int left, Buffer2D buffer, int length)
+ internal ResizeKernel(int index, int left, Buffer2D buffer, int length)
{
this.flatStartIndex = index * buffer.Width;
this.Left = left;
@@ -65,20 +65,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
///
- /// Gets the span representing the portion of the that this window covers
+ /// Gets the span representing the portion of the that this window covers
///
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span GetWindowSpan() => this.buffer.Span.Slice(this.flatStartIndex, this.Length);
+ public Span GetSpan() => this.buffer.Span.Slice(this.flatStartIndex, this.Length);
///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
+ /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
///
/// The input span of vectors
/// The source row position.
/// The weighted sum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX)
+ public Vector4 ConvolvePremultipliedRows(Span rowSpan, int sourceX)
{
ref float horizontalValues = ref this.GetStartReference();
int left = this.Left;
@@ -98,14 +98,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
+ /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
/// Applies to all input vectors.
///
/// The input span of vectors
/// The source row position.
/// The weighted sum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX)
+ public Vector4 ConvolvePremultipliedExpandedRows(Span rowSpan, int sourceX)
{
ref float horizontalValues = ref this.GetStartReference();
int left = this.Left;
@@ -126,14 +126,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x',
- /// weighted by weight values, pointed by this instance.
+ /// weighted by weight values, pointed by this instance.
///
/// The buffer of input vectors in row first order
/// The row position
/// The source column position.
/// The weighted sum
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY)
+ public Vector4 ConvolveColumnsAndUnPremultiply(Buffer2D firstPassPixels, int x, int sourceY)
{
ref float verticalValues = ref this.GetStartReference();
int left = this.Left;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
index 9b757f6e1b..52ed222cad 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
@@ -27,8 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
where TPixel : struct, IPixel
{
// The following fields are not immutable but are optionally created on demand.
- private WeightsBuffer horizontalWeights;
- private WeightsBuffer verticalWeights;
+ private KernelMap horizontalKernelMap;
+ private KernelMap verticalKernelMap;
///
/// Initializes a new instance of the class.
@@ -142,75 +142,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public bool Compand { get; }
- ///
- /// Computes the weights to apply at each pixel when resizing.
- ///
- /// The to use for buffer allocations
- /// The destination size
- /// The source size
- /// The
- // TODO: Made internal to simplify experimenting with weights data. Make it private when finished figuring out how to optimize all the stuff!
- internal WeightsBuffer PrecomputeWeights(MemoryAllocator memoryAllocator, int destinationSize, int sourceSize)
- {
- float ratio = (float)sourceSize / destinationSize;
- float scale = ratio;
-
- if (scale < 1F)
- {
- scale = 1F;
- }
-
- IResampler sampler = this.Sampler;
- float radius = MathF.Ceiling(scale * sampler.Radius);
- var result = new WeightsBuffer(memoryAllocator, destinationSize, radius);
-
- 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;
-
- WeightsWindow ws = result.GetWeightsWindow(i, left, right);
- result.Weights[i] = ws;
-
- ref float weightsBaseRef = ref ws.GetStartReference();
-
- for (int j = left; j <= right; j++)
- {
- float weight = sampler.GetValue((j - center) / scale);
- sum += weight;
-
- // weights[j - left] = weight:
- Unsafe.Add(ref weightsBaseRef, j - left) = weight;
- }
-
- // Normalize, best to do it here rather than in the pixel loop later on.
- if (sum > 0)
- {
- for (int w = 0; w < ws.Length; w++)
- {
- // weights[w] = weights[w] / sum:
- ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
- wRef /= sum;
- }
- }
- }
-
- return result;
- }
///
protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
@@ -229,15 +160,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
// Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
- this.horizontalWeights = this.PrecomputeWeights(
- memoryAllocator,
+ this.horizontalKernelMap = KernelMap.Calculate(
+ this.Sampler,
this.ResizeRectangle.Width,
- sourceRectangle.Width);
+ sourceRectangle.Width,
+ memoryAllocator);
- this.verticalWeights = this.PrecomputeWeights(
- memoryAllocator,
+ this.verticalKernelMap = KernelMap.Calculate(
+ this.Sampler,
this.ResizeRectangle.Height,
- sourceRectangle.Height);
+ sourceRectangle.Height,
+ memoryAllocator);
}
}
@@ -326,18 +259,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
for (int x = minX; x < maxX; x++)
{
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
+ ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX];
Unsafe.Add(ref firstPassRow, x) =
- window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX);
+ window.ConvolvePremultipliedExpandedRows(tempRowSpan, sourceX);
}
}
else
{
for (int x = minX; x < maxX; x++)
{
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
+ ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX];
Unsafe.Add(ref firstPassRow, x) =
- window.ComputeWeightedRowSum(tempRowSpan, sourceX);
+ window.ConvolvePremultipliedRows(tempRowSpan, sourceX);
}
}
}
@@ -354,7 +287,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int y = rows.Min; y < rows.Max; y++)
{
// Ensure offsets are normalized for cropping and padding.
- WeightsWindow window = this.verticalWeights.Weights[y - startY];
+ ResizeKernel window = this.verticalKernelMap.Kernels[y - startY];
ref TPixel targetRow = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
if (this.Compand)
@@ -362,7 +295,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++)
{
// Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(
+ Vector4 destinationVector = window.ConvolveColumnsAndUnPremultiply(
firstPassPixels,
x,
sourceY);
@@ -377,7 +310,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++)
{
// Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(
+ Vector4 destinationVector = window.ConvolveColumnsAndUnPremultiply(
firstPassPixels,
x,
sourceY);
@@ -396,10 +329,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
base.AfterImageApply(source, destination, sourceRectangle);
// TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable!
- this.horizontalWeights?.Dispose();
- this.horizontalWeights = null;
- this.verticalWeights?.Dispose();
- this.verticalWeights = null;
+ this.horizontalKernelMap?.Dispose();
+ this.horizontalKernelMap = null;
+ this.verticalKernelMap?.Dispose();
+ this.verticalKernelMap = null;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
deleted file mode 100644
index 6acf38d119..0000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// Holds the values in an optimized contigous memory region.
- ///
- internal class WeightsBuffer : IDisposable
- {
- private readonly Buffer2D dataBuffer;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The to use for allocations.
- /// The size of the destination window
- /// The radius of the kernel
- public WeightsBuffer(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius)
- {
- int width = (int)Math.Ceiling(kernelRadius * 2);
- this.dataBuffer = memoryAllocator.Allocate2D(width, destinationSize, AllocationOptions.Clean);
- this.Weights = new WeightsWindow[destinationSize];
- }
-
- ///
- /// Gets the calculated values.
- ///
- public WeightsWindow[] Weights { get; }
-
- ///
- /// Disposes instance releasing it's backing buffer.
- ///
- public void Dispose()
- {
- this.dataBuffer.Dispose();
- }
-
- ///
- /// Slices a weights value at the given positions.
- ///
- /// The index in destination buffer
- /// The local left index value
- /// The local right index value
- /// The weights
- public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
- {
- return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
- }
- }
-}
\ 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
new file mode 100644
index 0000000000..b60853a80e
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+using System.Text;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.Primitives;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
+{
+ public class KernelMapTests
+ {
+ private ITestOutputHelper Output { get; }
+
+ public KernelMapTests(ITestOutputHelper output)
+ {
+ this.Output = output;
+ }
+
+ [Theory]
+ [InlineData(500, 200, nameof(KnownResamplers.Bicubic))]
+ [InlineData(50, 40, nameof(KnownResamplers.Bicubic))]
+ [InlineData(40, 30, nameof(KnownResamplers.Bicubic))]
+ [InlineData(500, 200, nameof(KnownResamplers.Lanczos8))]
+ [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)
+ {
+ var resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null);
+
+ var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
+
+ var bld = new StringBuilder();
+
+ foreach (ResizeKernel window in kernelMap.Kernels)
+ {
+ Span span = window.GetSpan();
+ for (int i = 0; i < window.Length; i++)
+ {
+ float value = span[i];
+ bld.Append($"{value,7:F4}");
+ 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
index 4a35d63020..f0062d7146 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
@@ -40,43 +40,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
});
}
- [Theory]
- [InlineData(500, 200, nameof(KnownResamplers.Bicubic))]
- [InlineData(50, 40, nameof(KnownResamplers.Bicubic))]
- [InlineData(40, 30, nameof(KnownResamplers.Bicubic))]
- [InlineData(500, 200, nameof(KnownResamplers.Lanczos8))]
- [InlineData(100, 80, nameof(KnownResamplers.Lanczos8))]
- [InlineData(100, 10, nameof(KnownResamplers.Lanczos8))]
- [InlineData(10, 100, nameof(KnownResamplers.Lanczos8))]
- public void PrintWeightsData(int srcSize, int destSize, string resamplerName)
- {
- var size = new Size(srcSize, srcSize);
- var resampler = (IResampler) typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null);
- var proc = new ResizeProcessor(resampler, destSize, destSize, size);
-
- WeightsBuffer weights = proc.PrecomputeWeights(Configuration.Default.MemoryAllocator, proc.Width, size.Width);
-
- var bld = new StringBuilder();
-
- foreach (WeightsWindow window in weights.Weights)
- {
- Span span = window.GetWindowSpan();
- for (int i = 0; i < window.Length; i++)
- {
- float value = span[i];
- bld.Append($"{value,7:F4}");
- bld.Append("| ");
- }
-
- bld.AppendLine();
- }
-
- string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintWeightsData));
- string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD";
-
- File.WriteAllText(fileName, bld.ToString());
-
- this.Output.WriteLine(bld.ToString());
- }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index a84adbe1c6..1e0f86dcb8 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
+
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
public class ResizeTests : FileTestBase