Browse Source

Cover KernelMap with tests

af/merge-core
Anton Firszov 8 years ago
parent
commit
f152d459de
  1. 18
      src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
  2. 8
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  3. 99
      tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.ReferenceKernelMap.cs
  4. 42
      tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs

18
src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs

@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
private readonly Buffer2D<float> data; private readonly Buffer2D<float> data;
private readonly ResizeKernel[] kernels;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="KernelMap"/> class. /// Initializes a new instance of the <see cref="KernelMap"/> class.
/// </summary> /// </summary>
@ -25,15 +27,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="kernelRadius">The radius of the kernel</param> /// <param name="kernelRadius">The radius of the kernel</param>
public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius) public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius)
{ {
this.DestinationSize = destinationSize;
int width = (int)Math.Ceiling(kernelRadius * 2); int width = (int)Math.Ceiling(kernelRadius * 2);
this.data = memoryAllocator.Allocate2D<float>(width, destinationSize, AllocationOptions.Clean); this.data = memoryAllocator.Allocate2D<float>(width, destinationSize, AllocationOptions.Clean);
this.Kernels = new ResizeKernel[destinationSize]; this.kernels = new ResizeKernel[destinationSize];
} }
/// <summary> public int DestinationSize { get; }
/// Gets the calculated <see cref="Kernels"/> values.
/// </summary>
public ResizeKernel[] Kernels { get; }
/// <summary> /// <summary>
/// Disposes <see cref="KernelMap"/> instance releasing it's backing buffer. /// Disposes <see cref="KernelMap"/> instance releasing it's backing buffer.
@ -43,6 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.data.Dispose(); this.data.Dispose();
} }
/// <summary>
/// Returns a <see cref="ResizeKernel"/> for an index value between 0 and destinationSize - 1.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
/// <summary> /// <summary>
/// Computes the weights to apply at each pixel when resizing. /// Computes the weights to apply at each pixel when resizing.
/// </summary> /// </summary>
@ -88,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
float sum = 0; float sum = 0;
ResizeKernel ws = result.CreateKernel(i, left, right); ResizeKernel ws = result.CreateKernel(i, left, right);
result.Kernels[i] = ws; result.kernels[i] = ws;
ref float weightsBaseRef = ref MemoryMarshal.GetReference(ws.GetValues()); ref float weightsBaseRef = ref MemoryMarshal.GetReference(ws.GetValues());

8
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -269,9 +269,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = minX; x < maxX; x++) 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) = 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++) for (int y = rows.Min; y < rows.Max; y++)
{ {
// Ensure offsets are normalized for cropping and padding. // 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); ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan);
@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Span<Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); Span<Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY);
// Destination color components // Destination color components
Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn); Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn);
} }
Vector4Utils.UnPremultiply(tempRowSpan); Vector4Utils.UnPremultiply(tempRowSpan);

99
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
{
/// <summary>
/// Simplified reference implementation for <see cref="KernelMap"/> functionality.
/// </summary>
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<ReferenceKernel>();
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;
}
}
}

42
tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs

@ -12,7 +12,7 @@ using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ {
public class KernelMapTests public partial class KernelMapTests
{ {
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
@ -30,34 +30,52 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[InlineData(100, 80, nameof(KnownResamplers.Lanczos8))] [InlineData(100, 80, nameof(KnownResamplers.Lanczos8))]
[InlineData(100, 10, nameof(KnownResamplers.Lanczos8))] [InlineData(100, 10, nameof(KnownResamplers.Lanczos8))]
[InlineData(10, 100, 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 resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null);
var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); 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(); 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}) || "); bld.Append($"({kernel.Left:D3}) || ");
Span<float> span = kernel.GetValues(); Span<float> span = kernel.GetValues();
for (int i = 0; i < kernel.Length; i++)
for (int j = 0; j < kernel.Length; j++)
{ {
float value = span[i]; float value = span[j];
bld.Append($"{value,7:F5}"); bld.Append($"{value,8:F5}");
bld.Append(" | "); bld.Append(" | ");
} }
bld.AppendLine(); bld.AppendLine();
} }
string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintKernelMap)); return bld.ToString();
string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD";
File.WriteAllText(fileName, bld.ToString());
this.Output.WriteLine(bld.ToString());
} }
} }
} }
Loading…
Cancel
Save