Browse Source

Fix precision loss.

pull/2793/head
James Jackson-South 4 months ago
parent
commit
6fd20f5817
  1. 1
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  2. 13
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
  3. 19
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  4. 5
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
  5. 142
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs

1
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

13
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++)
{
float center = (float)(((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:

19
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -144,9 +144,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;
@ -178,7 +182,8 @@ internal partial class ResizeKernelMap : IDisposable
scale,
radius,
period,
cornerInterval)
cornerInterval,
sourcePeriod)
: new ResizeKernelMap(
memoryAllocator,
sourceSize,
@ -213,8 +218,8 @@ internal partial class ResizeKernelMap : IDisposable
private ResizeKernel BuildKernel<TResampler>(in TResampler sampler, int destRowIndex, int dataRowIndex)
where TResampler : struct, IResampler
{
float center = (float)(((destRowIndex + .5) * this.ratio) - .5);
float scale = (float)this.scale;
double center = ((destRowIndex + .5) * this.ratio) - .5;
double scale = this.scale;
// Keep inside bounds.
int left = (int)TolerantMath.Ceiling(center - this.radius);
@ -236,7 +241,7 @@ internal partial class ResizeKernelMap : IDisposable
for (int j = left; j <= right; j++)
{
float value = sampler.GetValue((j - center) / scale);
float value = sampler.GetValue((float)((j - center) / scale));
sum += value;
kernelStart = value;
kernelStart = ref Unsafe.Add(ref kernelStart, 1);

5
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs

@ -39,10 +39,9 @@ public partial class ResizeKernelMapTests
List<ReferenceKernel> result = [];
float scale = (float)scaleD;
for (int i = 0; i < destinationSize; i++)
{
float center = (float)(((i + .5) * ratio) - .5);
double center = ((i + .5) * ratio) - .5;
// Keep inside bounds.
int left = (int)tolerantMath.Ceiling(center - radius);
@ -63,7 +62,7 @@ public partial class ResizeKernelMapTests
for (int j = left; j <= right; j++)
{
float weight = sampler.GetValue((j - center) / scale);
float weight = sampler.GetValue((float)((j - center) / scaleD));
sum += weight;
values[j - left] = weight;
}

142
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;
/// <summary>
/// resamplerName, srcSize, destSize
@ -24,71 +21,70 @@ public partial class ResizeKernelMapTests
public static readonly TheoryData<IResampler, int, int> 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<string, int, int> 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<string, int, int> GeneratedImageResizeData = GenerateImageResizeData();
[Theory(Skip = "Only for debugging and development")]
[MemberData(nameof(KernelMapData))]
public void PrintNonNormalizedKernelMap<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
{
ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate<TResampler>(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>(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!
@ -125,6 +119,7 @@ public partial class ResizeKernelMapTests
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
#endif
ApproximateFloatComparer comparer = new ApproximateFloatComparer(1e-6f);
for (int i = 0; i < kernelMap.DestinationLength; i++)
{
ResizeKernel kernel = kernelMap.GetKernel((uint)i);
@ -140,15 +135,10 @@ public partial class ResizeKernelMapTests
float[] expectedValues = referenceKernel.Values;
Span<float> actualValues;
ApproximateFloatComparer comparer;
if (ResizeKernel.IsHardwareAccelerated)
{
comparer = new ApproximateFloatComparer(1e-4f);
Assert.Equal(expectedValues.Length, kernel.Values.Length / 4);
int actualLength = referenceKernel.Length / 4;
actualValues = new float[expectedValues.Length];
for (int j = 0; j < expectedValues.Length; j++)
@ -158,7 +148,6 @@ public partial class ResizeKernelMapTests
}
else
{
comparer = new ApproximateFloatComparer(1e-6f);
actualValues = kernel.Values;
}
@ -220,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

Loading…
Cancel
Save