diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index b2d7d2116..e10afce2e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -6,14 +6,12 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// Points to a collection of of weights allocated in . /// - internal unsafe struct ResizeKernel + internal readonly unsafe struct ResizeKernel { private readonly float* bufferPtr; @@ -73,5 +71,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return result; } + + internal ResizeKernel AlterLeftValue(int left) + { + return new ResizeKernel(left, this.bufferPtr, this.Length); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs index 09ae79677..cf660a72f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.MosaicKernelMap.cs @@ -1,5 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. + +using System; + using SixLabors.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -42,9 +45,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.period = period; } + internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; + protected override void Initialize() { - base.Initialize(); + // Build top corner data + one period of the mosaic data: + int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; + + for (int i = 0; i < startOfFirstRepeatedMosaic; i++) + { + ResizeKernel kernel = this.BuildKernel(i, i); + this.kernels[i] = kernel; + } + + // Copy the mosaics: + int bottomStartDest = this.DestinationSize - this.cornerInterval; + for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) + { + float center = ((i + .5F) * this.ratio) - .5F; + int left = (int)MathF.Ceiling(center - this.radius); + ResizeKernel kernel = this.kernels[i - this.period]; + this.kernels[i] = kernel.AlterLeftValue(left); + } + + // Build bottom corner data: + int bottomStartData = this.cornerInterval + this.period; + for (int i = 0; i < this.cornerInterval; i++) + { + ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); + this.kernels[bottomStartDest + i] = kernel; + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index ebb15d376..262a39352 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -57,6 +57,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public int DestinationSize { get; } + internal virtual string Info => + $"radius:{this.radius}|sourceSize:{this.sourceSize}|destinationSize:{this.DestinationSize}|ratio:{this.ratio}|scale:{this.scale}"; + /// /// Disposes instance releasing it's backing buffer. /// @@ -97,11 +100,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int radius = (int)MathF.Ceiling(scale * sampler.Radius); int period = ImageMaths.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; float center0 = (ratio - 1) * 0.5f; - int cornerInterval = (int)MathF.Ceiling((radius - center0 - 1) / ratio); + float firstNonNegativeLeftVal = (radius - center0 - 1) / ratio; + int cornerInterval = (int)MathF.Ceiling(firstNonNegativeLeftVal); - bool useMosaic = 2 * (cornerInterval + period) < destinationSize; + // corner case for cornerInteval: + if (firstNonNegativeLeftVal == cornerInterval) + { + cornerInterval++; + } - useMosaic = false; + bool useMosaic = 2 * (cornerInterval + period) < destinationSize; ResizeKernelMap result = useMosaic ? new MosaicKernelMap( @@ -131,14 +139,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected virtual void Initialize() { - for (int destRowIndex = 0; destRowIndex < this.DestinationSize; destRowIndex++) + for (int i = 0; i < this.DestinationSize; i++) { - ResizeKernel kernel = this.BuildKernelRow(destRowIndex, destRowIndex); - this.kernels[destRowIndex] = kernel; + ResizeKernel kernel = this.BuildKernel(i, i); + this.kernels[i] = kernel; } } - private ResizeKernel BuildKernelRow(int destRowIndex, int dataRowIndex) + private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) { float center = ((destRowIndex + .5F) * this.ratio) - .5F; @@ -157,7 +165,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float sum = 0; - ResizeKernel kernel = this.GetKernel(dataRowIndex, left, right); + ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); ref float kernelBaseRef = ref MemoryMarshal.GetReference(kernel.Values); @@ -188,7 +196,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Returns a referencing values of /// at row . /// - private unsafe ResizeKernel GetKernel(int dataRowIndex, int left, int right) + private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) { int length = right - left + 1; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs index 7b997e33f..f0a0d738b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs @@ -48,6 +48,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { nameof(KnownResamplers.Lanczos3), 5, 25 }, { nameof(KnownResamplers.Lanczos3), 5, 50 }, + { nameof(KnownResamplers.Lanczos3), 25, 5 }, + { nameof(KnownResamplers.Lanczos3), 50, 5 }, + { nameof(KnownResamplers.Lanczos3), 49, 5 }, + { nameof(KnownResamplers.Lanczos3), 31, 5 }, + { nameof(KnownResamplers.Lanczos8), 500, 200 }, { nameof(KnownResamplers.Lanczos8), 100, 10 }, { nameof(KnownResamplers.Lanczos8), 100, 80 }, @@ -75,8 +80,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG + this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); - // this.Output.WriteLine($"Reference KernelMap:\n{PrintKernelMap(referenceMap)}\n"); #endif for (int i = 0; i < kernelMap.DestinationSize; i++) @@ -92,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms Assert.Equal(expectedValues.Length, actualValues.Length); - var comparer = new ApproximateFloatComparer(1e-6f); + var comparer = new ApproximateFloatComparer(1e-4f); for (int x = 0; x < expectedValues.Length; x++) { @@ -116,12 +121,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { var bld = new StringBuilder(); + if (kernelMap is ResizeKernelMap actualMap) + { + bld.AppendLine(actualMap.Info); + } + int destinationSize = getDestinationSize(kernelMap); for (int i = 0; i < destinationSize; i++) { ReferenceKernel kernel = getKernel(kernelMap, i); - bld.Append($"({kernel.Left:D3}) || "); + bld.Append($"[{i:D3}] (L{kernel.Left:D3}) || "); Span span = kernel.Values; for (int j = 0; j < kernel.Length; j++)