diff --git a/README.md b/README.md
index cf58b6b14b..dc30734792 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ SixLabors.ImageSharp
[](https://github.com/SixLabors/ImageSharp/actions)
-[](https://codecov.io/gh/SixLabors/ImageSharp)
+[](https://codecov.io/gh/SixLabors/ImageSharp)
[](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE)
[](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
diff --git a/shared-infrastructure b/shared-infrastructure
index 57699ffb79..a1d3ac2049 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 57699ffb797bc2389c5d6cbb3b1800f2eb5fb947
+Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d
diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
index ff28063f05..efe68977bb 100644
--- a/src/ImageSharp/Common/Helpers/Numerics.cs
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -1080,4 +1080,47 @@ internal static class Numerics
public static nuint Vector512Count
(int length)
where TVector : struct
=> (uint)length / (uint)Vector512.Count;
+
+ ///
+ /// Normalizes the values in a given .
+ ///
+ /// The sequence of values to normalize.
+ /// The sum of the values in .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Normalize(Span span, float sum)
+ {
+ if (Vector256.IsHardwareAccelerated)
+ {
+ ref float startRef = ref MemoryMarshal.GetReference(span);
+ ref float endRef = ref Unsafe.Add(ref startRef, span.Length & ~7);
+ Vector256 sum256 = Vector256.Create(sum);
+
+ while (Unsafe.IsAddressLessThan(ref startRef, ref endRef))
+ {
+ Unsafe.As>(ref startRef) /= sum256;
+ startRef = ref Unsafe.Add(ref startRef, (nuint)8);
+ }
+
+ if ((span.Length & 7) >= 4)
+ {
+ Unsafe.As>(ref startRef) /= sum256.GetLower();
+ startRef = ref Unsafe.Add(ref startRef, (nuint)4);
+ }
+
+ endRef = ref Unsafe.Add(ref startRef, span.Length & 3);
+
+ while (Unsafe.IsAddressLessThan(ref startRef, ref endRef))
+ {
+ startRef /= sum;
+ startRef = ref Unsafe.Add(ref startRef, (nuint)1);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ span[i] /= sum;
+ }
+ }
+ }
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
index 26cf60034d..a85487d1c1 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
@@ -5,7 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
+using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms;
@@ -14,11 +14,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms;
///
internal readonly unsafe struct ResizeKernel
{
+ ///
+ /// The buffer with the convolution factors.
+ /// Note that when FMA is supported, this is of size 4x that reported in .
+ ///
private readonly float* bufferPtr;
///
/// Initializes a new instance of the struct.
///
+ /// The starting index for the destination row.
+ /// The pointer to the buffer with the convolution factors.
+ /// The length of the kernel.
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel(int startIndex, float* bufferPtr, int length)
{
@@ -27,6 +34,15 @@ internal readonly unsafe struct ResizeKernel
this.Length = length;
}
+ ///
+ /// Gets a value indicating whether vectorization is supported.
+ ///
+ public static bool IsHardwareAccelerated
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Vector256.IsHardwareAccelerated;
+ }
+
///
/// Gets the start index for the destination row.
///
@@ -53,7 +69,15 @@ internal readonly unsafe struct ResizeKernel
public Span Values
{
[MethodImpl(InliningOptions.ShortMethod)]
- get => new(this.bufferPtr, this.Length);
+ get
+ {
+ if (Vector256.IsHardwareAccelerated)
+ {
+ return new(this.bufferPtr, this.Length * 4);
+ }
+
+ return new(this.bufferPtr, this.Length);
+ }
}
///
@@ -68,73 +92,45 @@ internal readonly unsafe struct ResizeKernel
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ConvolveCore(ref Vector4 rowStartRef)
{
- if (Avx2.IsSupported && Fma.IsSupported)
+ if (IsHardwareAccelerated)
{
float* bufferStart = this.bufferPtr;
- float* bufferEnd = bufferStart + (this.Length & ~3);
+ ref Vector4 rowEndRef = ref Unsafe.Add(ref rowStartRef, this.Length & ~3);
Vector256 result256_0 = Vector256.Zero;
Vector256 result256_1 = Vector256.Zero;
- ReadOnlySpan maskBytes =
- [
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 1, 0, 0, 0,
- 1, 0, 0, 0, 1, 0, 0, 0
- ];
- Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes));
- while (bufferStart < bufferEnd)
+ while (Unsafe.IsAddressLessThan(ref rowStartRef, ref rowEndRef))
{
- // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps
- // for the FMA operation, and execute it directly on the target register and reading directly from
- // memory for the first parameter. This skips initializing a SIMD register, and an extra copy.
- // The code below should compile in the following assembly on .NET 5 x64:
- //
- // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _]
- // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b]
- // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0
- //
- // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212.
- // Additionally, we're also unrolling two computations per each loop iterations to leverage the
- // fact that most CPUs have two ports to schedule multiply operations for FMA instructions.
- result256_0 = Fma.MultiplyAdd(
- Unsafe.As>(ref rowStartRef),
- Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask),
- result256_0);
-
- result256_1 = Fma.MultiplyAdd(
- Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)),
- Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask),
- result256_1);
-
- bufferStart += 4;
- rowStartRef = ref Unsafe.Add(ref rowStartRef, 4);
+ Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef);
+ Vector256 pixels256_1 = Unsafe.As>(ref Unsafe.Add(ref rowStartRef, (nuint)2));
+
+ result256_0 = Vector256_.MultiplyAdd(result256_0, Vector256.Load(bufferStart), pixels256_0);
+ result256_1 = Vector256_.MultiplyAdd(result256_1, Vector256.Load(bufferStart + 8), pixels256_1);
+
+ bufferStart += 16;
+ rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)4);
}
- result256_0 = Avx.Add(result256_0, result256_1);
+ result256_0 += result256_1;
if ((this.Length & 3) >= 2)
{
- result256_0 = Fma.MultiplyAdd(
- Unsafe.As>(ref rowStartRef),
- Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask),
- result256_0);
+ Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef);
+ result256_0 = Vector256_.MultiplyAdd(result256_0, Vector256.Load(bufferStart), pixels256_0);
- bufferStart += 2;
- rowStartRef = ref Unsafe.Add(ref rowStartRef, 2);
+ bufferStart += 8;
+ rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)2);
}
- Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper());
+ Vector128 result128 = result256_0.GetLower() + result256_0.GetUpper();
if ((this.Length & 1) != 0)
{
- result128 = Fma.MultiplyAdd(
- Unsafe.As>(ref rowStartRef),
- Vector128.Create(*bufferStart),
- result128);
+ Vector128 pixels128 = Unsafe.As>(ref rowStartRef);
+ result128 = Vector128_.MultiplyAdd(result128, Vector128.Load(bufferStart), pixels128);
}
- return *(Vector4*)&result128;
+ return result128.AsVector4();
}
else
{
@@ -149,7 +145,7 @@ internal readonly unsafe struct ResizeKernel
result += rowStartRef * *bufferStart;
bufferStart++;
- rowStartRef = ref Unsafe.Add(ref rowStartRef, 1);
+ rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)1);
}
return result;
@@ -160,17 +156,32 @@ internal readonly unsafe struct ResizeKernel
/// Copy the contents of altering
/// to the value .
///
+ /// The new value for .
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left)
=> new(left, this.bufferPtr, this.Length);
- internal void Fill(Span values)
+ internal void FillOrCopyAndExpand(Span values)
{
DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!");
- for (int i = 0; i < this.Length; i++)
+ if (IsHardwareAccelerated)
+ {
+ Vector4* bufferStart = (Vector4*)this.bufferPtr;
+ ref float valuesStart = ref MemoryMarshal.GetReference(values);
+ ref float valuesEnd = ref Unsafe.Add(ref valuesStart, values.Length);
+
+ while (Unsafe.IsAddressLessThan(ref valuesStart, ref valuesEnd))
+ {
+ *bufferStart = new Vector4(valuesStart);
+
+ bufferStart++;
+ valuesStart = ref Unsafe.Add(ref valuesStart, (nuint)1);
+ }
+ }
+ else
{
- this.Values[i] = (float)values[i];
+ values.CopyTo(this.Values);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
index ee1ada43ad..d606738abd 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
@@ -16,6 +16,8 @@ internal partial class ResizeKernelMap
private readonly int cornerInterval;
+ private readonly int sourcePeriod;
+
public PeriodicKernelMap(
MemoryAllocator memoryAllocator,
int sourceLength,
@@ -24,7 +26,8 @@ internal partial class ResizeKernelMap
double scale,
int radius,
int period,
- int cornerInterval)
+ int cornerInterval,
+ int sourcePeriod)
: base(
memoryAllocator,
sourceLength,
@@ -36,6 +39,7 @@ internal partial class ResizeKernelMap
{
this.cornerInterval = cornerInterval;
this.period = period;
+ this.sourcePeriod = sourcePeriod;
}
internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}";
@@ -54,10 +58,11 @@ internal partial class ResizeKernelMap
int bottomStartDest = this.DestinationLength - this.cornerInterval;
for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++)
{
- double center = ((i + .5) * this.ratio) - .5;
- int left = (int)TolerantMath.Ceiling(center - this.radius);
ResizeKernel kernel = this.kernels[i - this.period];
- this.kernels[i] = kernel.AlterLeftValue(left);
+
+ // Shift the kernel start index by the source-side period so the same weights align to the
+ // next repeated sampling window in the source image.
+ this.kernels[i] = kernel.AlterLeftValue(kernel.StartIndex + this.sourcePeriod);
}
// Build bottom corner data:
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
index bd4a18c2fb..0b8106e0be 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
@@ -33,7 +33,7 @@ internal partial class ResizeKernelMap : IDisposable
private bool isDisposed;
// To avoid both GC allocations, and MemoryAllocator ceremony:
- private readonly double[] tempValues;
+ private readonly float[] tempValues;
private ResizeKernelMap(
MemoryAllocator memoryAllocator,
@@ -50,10 +50,12 @@ internal partial class ResizeKernelMap : IDisposable
this.sourceLength = sourceLength;
this.DestinationLength = destinationLength;
this.MaxDiameter = (radius * 2) + 1;
- this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean);
+
+ int diameter = ResizeKernel.IsHardwareAccelerated ? this.MaxDiameter * 4 : this.MaxDiameter;
+ this.data = memoryAllocator.Allocate2D(diameter, bufferHeight, preferContiguosImageBuffers: true);
this.pinHandle = this.data.DangerousGetSingleMemory().Pin();
this.kernels = new ResizeKernel[destinationLength];
- this.tempValues = new double[this.MaxDiameter];
+ this.tempValues = new float[this.MaxDiameter];
}
///
@@ -135,9 +137,13 @@ internal partial class ResizeKernelMap : IDisposable
int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius);
// 'ratio' is a rational number.
- // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again".
- // This value is determining the length of the periods in repeating kernel map rows.
- int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize);
+ // Multiplying it by destSize/GCD(sourceSize, destinationSize) yields an integer, so every `period` rows
+ // the destination-space sampling centers repeat their fractional alignment. `period` is the repeat length
+ // in destination rows, while `sourcePeriod` is the corresponding integer offset in source pixels that
+ // must be added to the kernel's left index when we reuse the same weights from a previous period.
+ int gcd = Numerics.GreatestCommonDivisor(sourceSize, destinationSize);
+ int period = destinationSize / gcd;
+ int sourcePeriod = sourceSize / gcd;
// the center position at i == 0:
double center0 = (ratio - 1) * 0.5;
@@ -161,23 +167,24 @@ internal partial class ResizeKernelMap : IDisposable
bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize;
ResizeKernelMap result = hasAtLeast2Periods
- ? new PeriodicKernelMap(
- memoryAllocator,
- sourceSize,
- destinationSize,
- ratio,
- scale,
- radius,
- period,
- cornerInterval)
- : new ResizeKernelMap(
- memoryAllocator,
- sourceSize,
- destinationSize,
- destinationSize,
- ratio,
- scale,
- radius);
+ ? new PeriodicKernelMap(
+ memoryAllocator,
+ sourceSize,
+ destinationSize,
+ ratio,
+ scale,
+ radius,
+ period,
+ cornerInterval,
+ sourcePeriod)
+ : new ResizeKernelMap(
+ memoryAllocator,
+ sourceSize,
+ destinationSize,
+ destinationSize,
+ ratio,
+ scale,
+ radius);
result.Initialize(in sampler);
@@ -205,6 +212,7 @@ internal partial class ResizeKernelMap : IDisposable
where TResampler : struct, IResampler
{
double center = ((destRowIndex + .5) * this.ratio) - .5;
+ double scale = this.scale;
// Keep inside bounds.
int left = (int)TolerantMath.Ceiling(center - this.radius);
@@ -220,30 +228,25 @@ internal partial class ResizeKernelMap : IDisposable
}
ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right);
-
- Span kernelValues = this.tempValues.AsSpan(0, kernel.Length);
- double sum = 0;
+ Span kernelValues = this.tempValues.AsSpan(0, kernel.Length);
+ ref float kernelStart = ref MemoryMarshal.GetReference(kernelValues);
+ float sum = 0;
for (int j = left; j <= right; j++)
{
- double value = sampler.GetValue((float)((j - center) / this.scale));
+ float value = sampler.GetValue((float)((j - center) / scale));
sum += value;
-
- kernelValues[j - left] = value;
+ kernelStart = value;
+ kernelStart = ref Unsafe.Add(ref kernelStart, 1);
}
// Normalize, best to do it here rather than in the pixel loop later on.
if (sum > 0)
{
- for (int j = 0; j < kernel.Length; j++)
- {
- // weights[w] = weights[w] / sum:
- ref double kRef = ref kernelValues[j];
- kRef /= sum;
- }
+ Numerics.Normalize(kernelValues, sum);
}
- kernel.Fill(kernelValues);
+ kernel.FillOrCopyAndExpand(kernelValues);
return kernel;
}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
index 00ad71b4ca..e38bf64c1b 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
@@ -16,9 +16,7 @@ public partial class ResizeKernelMapTests
private readonly ReferenceKernel[] kernels;
public ReferenceKernelMap(ReferenceKernel[] kernels)
- {
- this.kernels = kernels;
- }
+ => this.kernels = kernels;
public int DestinationSize => this.kernels.Length;
@@ -28,16 +26,16 @@ public partial class ResizeKernelMapTests
where TResampler : struct, IResampler
{
double ratio = (double)sourceSize / destinationSize;
- double scale = ratio;
+ double scaleD = ratio;
- if (scale < 1F)
+ if (scaleD < 1)
{
- scale = 1F;
+ scaleD = 1;
}
TolerantMath tolerantMath = TolerantMath.Default;
- double radius = tolerantMath.Ceiling(scale * sampler.Radius);
+ double radius = tolerantMath.Ceiling(scaleD * sampler.Radius);
List result = [];
@@ -58,15 +56,14 @@ public partial class ResizeKernelMapTests
right = sourceSize - 1;
}
- double sum = 0;
+ float sum = 0;
- double[] values = new double[right - left + 1];
+ float[] values = new float[right - left + 1];
for (int j = left; j <= right; j++)
{
- double weight = sampler.GetValue((float)((j - center) / scale));
+ float weight = sampler.GetValue((float)((j - center) / scaleD));
sum += weight;
-
values[j - left] = weight;
}
@@ -78,16 +75,14 @@ public partial class ResizeKernelMapTests
}
}
- float[] floatVals = values.Select(v => (float)v).ToArray();
-
- result.Add(new ReferenceKernel(left, floatVals));
+ result.Add(new ReferenceKernel(left, values));
}
- return new ReferenceKernelMap(result.ToArray());
+ return new ReferenceKernelMap([.. result]);
}
}
- internal struct ReferenceKernel
+ internal readonly struct ReferenceKernel
{
public ReferenceKernel(int left, float[] values)
{
@@ -102,8 +97,6 @@ public partial class ResizeKernelMapTests
public int Length => this.Values.Length;
public static implicit operator ReferenceKernel(ResizeKernel orig)
- {
- return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray());
- }
+ => new(orig.StartIndex, orig.Values.ToArray());
}
}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
index 0357dda046..5f9a8055ff 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
@@ -13,10 +13,7 @@ public partial class ResizeKernelMapTests
{
private ITestOutputHelper Output { get; }
- public ResizeKernelMapTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
+ public ResizeKernelMapTests(ITestOutputHelper output) => this.Output = output;
///
/// resamplerName, srcSize, destSize
@@ -24,71 +21,70 @@ public partial class ResizeKernelMapTests
public static readonly TheoryData KernelMapData
= new()
{
- { KnownResamplers.Bicubic, 15, 10 },
- { KnownResamplers.Bicubic, 10, 15 },
- { KnownResamplers.Bicubic, 20, 20 },
- { KnownResamplers.Bicubic, 50, 40 },
- { KnownResamplers.Bicubic, 40, 50 },
- { KnownResamplers.Bicubic, 500, 200 },
- { KnownResamplers.Bicubic, 200, 500 },
- { KnownResamplers.Bicubic, 3032, 400 },
- { KnownResamplers.Bicubic, 10, 25 },
- { KnownResamplers.Lanczos3, 16, 12 },
- { KnownResamplers.Lanczos3, 12, 16 },
- { KnownResamplers.Lanczos3, 12, 9 },
- { KnownResamplers.Lanczos3, 9, 12 },
- { KnownResamplers.Lanczos3, 6, 8 },
- { KnownResamplers.Lanczos3, 8, 6 },
- { KnownResamplers.Lanczos3, 20, 12 },
- { KnownResamplers.Lanczos3, 5, 25 },
- { KnownResamplers.Lanczos3, 5, 50 },
- { KnownResamplers.Lanczos3, 25, 5 },
- { KnownResamplers.Lanczos3, 50, 5 },
- { KnownResamplers.Lanczos3, 49, 5 },
- { KnownResamplers.Lanczos3, 31, 5 },
- { KnownResamplers.Lanczos8, 500, 200 },
- { KnownResamplers.Lanczos8, 100, 10 },
- { KnownResamplers.Lanczos8, 100, 80 },
- { KnownResamplers.Lanczos8, 10, 100 },
-
- // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5:
- { KnownResamplers.Box, 378, 149 },
- { KnownResamplers.Box, 349, 174 },
-
- // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData
- { KnownResamplers.Box, 201, 100 },
- { KnownResamplers.Box, 199, 99 },
- { KnownResamplers.Box, 10, 299 },
- { KnownResamplers.Box, 299, 10 },
- { KnownResamplers.Box, 301, 300 },
- { KnownResamplers.Box, 1180, 480 },
- { KnownResamplers.Lanczos2, 3264, 3032 },
- { KnownResamplers.Bicubic, 1280, 2240 },
- { KnownResamplers.Bicubic, 1920, 1680 },
- { KnownResamplers.Bicubic, 3072, 2240 },
- { KnownResamplers.Welch, 300, 2008 },
-
- // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData
- { KnownResamplers.Bicubic, 10, 50 },
- { KnownResamplers.Bicubic, 49, 301 },
- { KnownResamplers.Bicubic, 301, 49 },
- { KnownResamplers.Bicubic, 1680, 1200 },
- { KnownResamplers.Box, 13, 299 },
- { KnownResamplers.Lanczos5, 3032, 600 },
-
- // Large number. https://github.com/SixLabors/ImageSharp/issues/1616
- { KnownResamplers.Bicubic, 207773, 51943 }
- };
-
- public static TheoryData GeneratedImageResizeData =
- GenerateImageResizeData();
+ { KnownResamplers.Bicubic, 15, 10 },
+ { KnownResamplers.Bicubic, 10, 15 },
+ { KnownResamplers.Bicubic, 20, 20 },
+ { KnownResamplers.Bicubic, 50, 40 },
+ { KnownResamplers.Bicubic, 40, 50 },
+ { KnownResamplers.Bicubic, 500, 200 },
+ { KnownResamplers.Bicubic, 200, 500 },
+ { KnownResamplers.Bicubic, 3032, 400 },
+ { KnownResamplers.Bicubic, 10, 25 },
+ { KnownResamplers.Lanczos3, 16, 12 },
+ { KnownResamplers.Lanczos3, 12, 16 },
+ { KnownResamplers.Lanczos3, 12, 9 },
+ { KnownResamplers.Lanczos3, 9, 12 },
+ { KnownResamplers.Lanczos3, 6, 8 },
+ { KnownResamplers.Lanczos3, 8, 6 },
+ { KnownResamplers.Lanczos3, 20, 12 },
+ { KnownResamplers.Lanczos3, 5, 25 },
+ { KnownResamplers.Lanczos3, 5, 50 },
+ { KnownResamplers.Lanczos3, 25, 5 },
+ { KnownResamplers.Lanczos3, 50, 5 },
+ { KnownResamplers.Lanczos3, 49, 5 },
+ { KnownResamplers.Lanczos3, 31, 5 },
+ { KnownResamplers.Lanczos8, 500, 200 },
+ { KnownResamplers.Lanczos8, 100, 10 },
+ { KnownResamplers.Lanczos8, 100, 80 },
+ { KnownResamplers.Lanczos8, 10, 100 },
+
+ // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5:
+ { KnownResamplers.Box, 378, 149 },
+ { KnownResamplers.Box, 349, 174 },
+
+ // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData
+ { KnownResamplers.Box, 201, 100 },
+ { KnownResamplers.Box, 199, 99 },
+ { KnownResamplers.Box, 10, 299 },
+ { KnownResamplers.Box, 299, 10 },
+ { KnownResamplers.Box, 301, 300 },
+ { KnownResamplers.Box, 1180, 480 },
+ { KnownResamplers.Lanczos2, 3264, 3032 },
+ { KnownResamplers.Bicubic, 1280, 2240 },
+ { KnownResamplers.Bicubic, 1920, 1680 },
+ { KnownResamplers.Bicubic, 3072, 2240 },
+ { KnownResamplers.Welch, 300, 2008 },
+
+ // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData
+ { KnownResamplers.Bicubic, 10, 50 },
+ { KnownResamplers.Bicubic, 49, 301 },
+ { KnownResamplers.Bicubic, 301, 49 },
+ { KnownResamplers.Bicubic, 1680, 1200 },
+ { KnownResamplers.Box, 13, 299 },
+ { KnownResamplers.Lanczos5, 3032, 600 },
+
+ // Large number. https://github.com/SixLabors/ImageSharp/issues/1616
+ { KnownResamplers.Bicubic, 207773, 51943 }
+ };
+
+ public static TheoryData GeneratedImageResizeData = GenerateImageResizeData();
[Theory(Skip = "Only for debugging and development")]
[MemberData(nameof(KernelMapData))]
public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
{
- ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false);
+ ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false);
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
}
@@ -97,9 +93,7 @@ public partial class ResizeKernelMapTests
[MemberData(nameof(KernelMapData))]
public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
- {
- this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize);
- }
+ => this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize);
// Comprehensive but expensive tests, for ResizeKernelMap.
// Enabling them can kill you, but sometimes you have to wear the burden!
@@ -124,8 +118,8 @@ public partial class ResizeKernelMapTests
this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n");
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
#endif
- ApproximateFloatComparer comparer = new(1e-6f);
+ ApproximateFloatComparer comparer = new ApproximateFloatComparer(1e-6f);
for (int i = 0; i < kernelMap.DestinationLength; i++)
{
ResizeKernel kernel = kernelMap.GetKernel((uint)i);
@@ -139,7 +133,23 @@ public partial class ResizeKernelMapTests
referenceKernel.Left == kernel.StartIndex,
$"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}");
float[] expectedValues = referenceKernel.Values;
- Span actualValues = kernel.Values;
+ Span actualValues;
+
+ if (ResizeKernel.IsHardwareAccelerated)
+ {
+ Assert.Equal(expectedValues.Length, kernel.Values.Length / 4);
+
+ actualValues = new float[expectedValues.Length];
+
+ for (int j = 0; j < expectedValues.Length; j++)
+ {
+ actualValues[j] = kernel.Values[j * 4];
+ }
+ }
+ else
+ {
+ actualValues = kernel.Values;
+ }
Assert.Equal(expectedValues.Length, actualValues.Length);
@@ -199,12 +209,13 @@ public partial class ResizeKernelMapTests
int[] dimensionVals =
[
+
// Arbitrary, small dimensions:
- 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301,
+ 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301,
- // Typical image sizes:
- 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560,
- 1920, 3032, 2008, 3072, 2304, 3264, 2448
+ // Typical image sizes:
+ 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560,
+ 1920, 3032, 2008, 3072, 2304, 3264, 2448
];
IOrderedEnumerable<(int S, int D)> source2Dest = dimensionVals