Browse Source

Inline resizing sampler.

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
52e02a3298
  1. 18
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  2. 2
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  3. 2
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  4. 19
      src/ImageSharp/Processing/Processors/Transforms/IResampler.cs
  5. 2
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  6. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs
  7. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs
  8. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs
  9. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs
  10. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs
  11. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs
  12. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs
  13. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs
  14. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs
  15. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs
  16. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs
  17. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs
  18. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs
  19. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs
  20. 18
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
  21. 227
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs
  22. 21
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  23. 24
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
  24. 55
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  25. 16
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  26. 178
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  27. 13
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  28. 7
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
  29. 129
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
  30. 4
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  31. 8
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  32. 16
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

18
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
/// <summary>
/// Gets the size of the target image.
/// Gets the size of the destination image.
/// </summary>
/// <returns>The <see cref="Size"/>.</returns>
protected abstract Size GetTargetSize();
protected abstract Size GetDestinationSize();
/// <summary>
/// This method is called before the process is applied to prepare the processor.
@ -168,21 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
private Image<TPixel> CreateTarget()
{
Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize();
Size destinationSize = this.GetDestinationSize();
// We will always be creating the clone even for mutate because we may need to resize the canvas.
var targetFrames = new ImageFrame<TPixel>[source.Frames.Count];
for (int i = 0; i < targetFrames.Length; i++)
var destinationFrames = new ImageFrame<TPixel>[source.Frames.Count];
for (int i = 0; i < destinationFrames.Length; i++)
{
targetFrames[i] = new ImageFrame<TPixel>(
destinationFrames[i] = new ImageFrame<TPixel>(
this.Configuration,
targetSize.Width,
targetSize.Height,
destinationSize.Width,
destinationSize.Height,
source.Frames[i].Metadata.DeepClone());
}
// Use the overload to prevent an extra frame being added.
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), targetFrames);
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), destinationFrames);
}
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)

2
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.resampler = definition.Sampler;
}
protected override Size GetTargetSize() => this.targetSize;
protected override Size GetDestinationSize() => this.targetSize;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)

2
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
=> this.cropRectangle = definition.CropRectangle;
/// <inheritdoc/>
protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)

19
src/ImageSharp/Processing/Processors/Transforms/IResampler.cs

@ -25,6 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </returns>
float GetValue(float x);
/// <summary>
/// Applies an resizing transformation upon an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source image.</param>
/// <param name="destination">The destination image.</param>
/// <param name="sourceRectangle">The source bounds.</param>
/// <param name="targetRectangle">The target location.</param>
/// <param name="compand">Whether to compress or expand individual pixel color values on processing.</param>
void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle targetRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Applies an affine transformation upon an image.
/// </summary>

2
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.resampler = definition.Sampler;
}
protected override Size GetTargetSize() => this.targetSize;
protected override Size GetDestinationSize() => this.targetSize;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs

@ -41,6 +41,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs

@ -28,6 +28,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs

@ -28,6 +28,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs

@ -27,6 +27,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs

@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs

@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs

@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs

@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs

@ -25,6 +25,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs

@ -20,6 +20,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x) => x;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs

@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs

@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs

@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs

@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

18
src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs

@ -33,6 +33,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyResizeTransform<TPixel>(
Configuration configuration,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyResizeTransform(
configuration,
in this,
source,
destination,
sourceRectangle,
destinationRectangle,
compand);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(

227
src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs

@ -0,0 +1,227 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <content>
/// Extensions for <see cref="IResampler"/>.
/// </content>
public static partial class ResamplerExtensions
{
/// <summary>
/// Applies an resizing transformation upon an image.
/// </summary>
/// <typeparam name="TResampler">The type of sampler.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="sampler">The pixel sampler.</param>
/// <param name="source">The source image.</param>
/// <param name="destination">The destination image.</param>
/// <param name="sourceRectangle">The source bounds.</param>
/// <param name="destinationRectangle">The destination location.</param>
/// <param name="compand">Whether to compress or expand individual pixel color values on processing.</param>
public static void ApplyResizeTransform<TResampler, TPixel>(
Configuration configuration,
in TResampler sampler,
Image<TPixel> source,
Image<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
bool compand)
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
&& source.Height == destination.Height
&& sourceRectangle == destinationRectangle)
{
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
// The cloned will be blank here copy all the pixel data over
sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup());
}
return;
}
var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds());
if (sampler is NearestNeighborResampler)
{
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
ApplyNNResizeFrameTransform(
configuration,
sourceFrame,
destinationFrame,
sourceRectangle,
destinationRectangle,
interest);
}
return;
}
// Since all image frame dimensions have to be the same we can calculate
// the kernel maps and reuse for all frames.
MemoryAllocator allocator = configuration.MemoryAllocator;
using var horizontalKernelMap = ResizeKernelMap<TResampler>.Calculate(
in sampler,
destinationRectangle.Width,
sourceRectangle.Width,
allocator);
using var verticalKernelMap = ResizeKernelMap<TResampler>.Calculate(
in sampler,
destinationRectangle.Height,
sourceRectangle.Height,
allocator);
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
ApplyResizeFrameTransform(
configuration,
sourceFrame,
destinationFrame,
horizontalKernelMap,
verticalKernelMap,
sourceRectangle,
destinationRectangle,
interest,
compand);
}
}
private static void ApplyNNResizeFrameTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
Rectangle interest)
where TPixel : struct, IPixel<TPixel>
{
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height;
var operation = new NNRowIntervalOperation<TPixel>(
sourceRectangle,
destinationRectangle,
widthFactor,
heightFactor,
source,
destination);
ParallelRowIterator.IterateRows(
configuration,
interest,
in operation);
}
private static void ApplyResizeFrameTransform<TResampler, TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
ResizeKernelMap<TResampler> horizontalKernelMap,
ResizeKernelMap<TResampler> verticalKernelMap,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
Rectangle interest,
bool compand)
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
// To reintroduce parallel processing, we would launch multiple workers
// for different row intervals of the image.
using (var worker = new ResizeWorker<TResampler, TPixel>(
configuration,
sourceArea,
conversionModifiers,
horizontalKernelMap,
verticalKernelMap,
destination.Width,
interest,
destinationRectangle.Location))
{
worker.Initialize();
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
}
private readonly struct NNRowIntervalOperation<TPixel> : IRowIntervalOperation
where TPixel : struct, IPixel<TPixel>
{
private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds;
private readonly float widthFactor;
private readonly float heightFactor;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NNRowIntervalOperation(
Rectangle sourceBounds,
Rectangle destinationBounds,
float widthFactor,
float heightFactor,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.sourceBounds = sourceBounds;
this.destinationBounds = destinationBounds;
this.widthFactor = widthFactor;
this.heightFactor = heightFactor;
this.source = source;
this.destination = destination;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
int sourceX = this.sourceBounds.X;
int sourceY = this.sourceBounds.Y;
int destX = this.destinationBounds.X;
int destY = this.destinationBounds.Y;
int destLeft = this.destinationBounds.Left;
int destRight = this.destinationBounds.Right;
for (int y = rows.Min; y < rows.Max; y++)
{
// Y coordinates of source points
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y);
for (int x = destLeft; x < destRight; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)];
}
}
}
}
}
}

21
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Points to a collection of of weights allocated in <see cref="ResizeKernelMap"/>.
/// Points to a collection of of weights allocated in <see cref="ResizeKernelMap{T}"/>.
/// </summary>
internal readonly unsafe struct ResizeKernel
{
@ -28,15 +28,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Gets the start index for the destination row.
/// </summary>
public int StartIndex { get; }
public int StartIndex
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary>
/// Gets the the length of the kernel.
/// </summary>
public int Length { get; }
public int Length
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary>
/// Gets the span representing the portion of the <see cref="ResizeKernelMap"/> that this window covers.
/// Gets the span representing the portion of the <see cref="ResizeKernelMap{T}"/> that this window covers.
/// </summary>
/// <value>The <see cref="Span{T}"/>.
/// </value>
@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Copy the contents of <see cref="ResizeKernel"/> altering <see cref="StartIndex"/>
/// to the value <paramref name="left"/>.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left)
{
return new ResizeKernel(left, this.bufferPtr, this.Length);
@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
}
}
}

24
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs

@ -1,19 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <content>
/// Contains <see cref="PeriodicKernelMap"/>
/// </content>
internal partial class ResizeKernelMap
internal partial class ResizeKernelMap<TResampler>
where TResampler : unmanaged, IResampler
{
/// <summary>
/// Memory-optimized <see cref="ResizeKernelMap"/> where repeating rows are stored only once.
/// Memory-optimized <see cref="ResizeKernelMap{TResampler}"/> where repeating rows are stored only once.
/// </summary>
private sealed class PeriodicKernelMap : ResizeKernelMap
private sealed class PeriodicKernelMap : ResizeKernelMap<TResampler>
{
private readonly int period;
@ -21,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public PeriodicKernelMap(
MemoryAllocator memoryAllocator,
IResampler sampler,
TResampler sampler,
int sourceLength,
int destinationLength,
double ratio,
@ -45,15 +43,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}";
protected override void Initialize()
protected internal override void 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;
this.kernels[i] = this.BuildKernel(i, i);
}
// Copy the mosaics:
@ -70,10 +67,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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;
this.kernels[bottomStartDest + i] = this.BuildKernel(bottomStartDest + i, bottomStartData + i);
}
}
}
}
}
}

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

@ -5,20 +5,20 @@ using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Provides <see cref="ResizeKernel"/> values from an optimized,
/// contiguous memory region.
/// Provides resize kernel values from an optimized contiguous memory region.
/// </summary>
internal partial class ResizeKernelMap : IDisposable
/// <typeparam name="TResampler">The type of sampler.</typeparam>
internal partial class ResizeKernelMap<TResampler> : IDisposable
where TResampler : unmanaged, IResampler
{
private static readonly TolerantMath TolerantMath = TolerantMath.Default;
private readonly IResampler sampler;
private readonly TResampler sampler;
private readonly int sourceLength;
@ -34,12 +34,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernel[] kernels;
private bool isDisposed;
// To avoid both GC allocations, and MemoryAllocator ceremony:
private readonly double[] tempValues;
private ResizeKernelMap(
MemoryAllocator memoryAllocator,
IResampler sampler,
TResampler sampler,
int sourceLength,
int destinationLength,
int bufferHeight,
@ -77,19 +79,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
$"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}";
/// <summary>
/// Disposes <see cref="ResizeKernelMap"/> instance releasing it's backing buffer.
/// Disposes <see cref="ResizeKernelMap{TResampler}"/> instance releasing it's backing buffer.
/// </summary>
public void Dispose()
=> this.Dispose(true);
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
this.pinHandle.Dispose();
this.data.Dispose();
if (!this.isDisposed)
{
this.isDisposed = true;
if (disposing)
{
this.pinHandle.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];
internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
@ -98,9 +115,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param>
/// <returns>The <see cref="ResizeKernelMap"/></returns>
public static ResizeKernelMap Calculate(
IResampler sampler,
/// <returns>The <see cref="ResizeKernelMap{IResampler}"/></returns>
public static ResizeKernelMap<TResampler> Calculate(
in TResampler sampler,
int destinationSize,
int sourceSize,
MemoryAllocator memoryAllocator)
@ -141,7 +158,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// If we don't have at least 2 periods, we go with the basic implementation:
bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize;
ResizeKernelMap result = hasAtLeast2Periods
ResizeKernelMap<TResampler> result = hasAtLeast2Periods
? new PeriodicKernelMap(
memoryAllocator,
sampler,
@ -152,7 +169,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
radius,
period,
cornerInterval)
: new ResizeKernelMap(
: new ResizeKernelMap<TResampler>(
memoryAllocator,
sampler,
sourceSize,
@ -167,12 +184,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return result;
}
protected virtual void Initialize()
/// <summary>
/// Initializes the kernel map.
/// </summary>
protected internal virtual void Initialize()
{
for (int i = 0; i < this.DestinationLength; i++)
{
ResizeKernel kernel = this.BuildKernel(i, i);
this.kernels[i] = kernel;
this.kernels[i] = this.BuildKernel(i, i);
}
}

16
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -21,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
this.Sampler = options.Sampler;
this.TargetWidth = size.Width;
this.TargetHeight = size.Height;
this.TargetRectangle = rectangle;
this.DestinationWidth = size.Width;
this.DestinationHeight = size.Height;
this.DestinationRectangle = rectangle;
this.Compand = options.Compand;
}
@ -33,19 +33,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public IResampler Sampler { get; }
/// <summary>
/// Gets the target width.
/// Gets the destination width.
/// </summary>
public int TargetWidth { get; }
public int DestinationWidth { get; }
/// <summary>
/// Gets the target height.
/// Gets the destination height.
/// </summary>
public int TargetHeight { get; }
public int DestinationHeight { get; }
/// <summary>
/// Gets the resize rectangle.
/// </summary>
public Rectangle TargetRectangle { get; }
public Rectangle DestinationRectangle { get; }
/// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.

178
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -1,10 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -12,59 +8,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Implements resizing of images using various resamplers.
/// </summary>
/// <remarks>
/// The original code has been adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
private readonly int targetWidth;
private readonly int targetHeight;
private readonly int destinationWidth;
private readonly int destinationHeight;
private readonly IResampler resampler;
private readonly Rectangle targetRectangle;
private readonly Rectangle destinationRectangle;
private readonly bool compand;
// The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.targetWidth = definition.TargetWidth;
this.targetHeight = definition.TargetHeight;
this.targetRectangle = definition.TargetRectangle;
this.destinationWidth = definition.DestinationWidth;
this.destinationHeight = definition.DestinationHeight;
this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler;
this.compand = definition.Compand;
}
/// <inheritdoc/>
protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight);
protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight);
/// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination)
{
if (!(this.resampler is NearestNeighborResampler))
{
Image<TPixel> source = this.Source;
Rectangle sourceRectangle = this.SourceRectangle;
// Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.resampler,
this.targetRectangle.Width,
sourceRectangle.Width,
memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate(
this.resampler,
this.targetRectangle.Height,
sourceRectangle.Height,
memoryAllocator);
}
this.resampler.ApplyResizeTransform(
this.Configuration,
this.Source,
destination,
this.SourceRectangle,
this.destinationRectangle,
this.compand);
base.BeforeImageApply(destination);
}
@ -72,131 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
&& source.Height == destination.Height
&& sourceRectangle == this.targetRectangle)
{
// The cloned will be blank here copy all the pixel data over
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}
int width = this.targetWidth;
int height = this.targetHeight;
var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height));
if (this.resampler is NearestNeighborResampler)
{
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination);
ParallelRowIterator.IterateRows(
configuration,
interest,
in operation);
return;
}
PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
// To reintroduce parallel processing, we to launch multiple workers
// for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>(
configuration,
sourceArea,
conversionModifiers,
this.horizontalKernelMap,
this.verticalKernelMap,
width,
interest,
this.targetRectangle.Location))
{
worker.Initialize();
var workingInterval = new RowInterval(interest.Top, interest.Bottom);
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.horizontalKernelMap?.Dispose();
this.horizontalKernelMap = null;
this.verticalKernelMap?.Dispose();
this.verticalKernelMap = null;
}
this.isDisposed = true;
base.Dispose(disposing);
}
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds;
private readonly float widthFactor;
private readonly float heightFactor;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle sourceBounds,
Rectangle destinationBounds,
float widthFactor,
float heightFactor,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.sourceBounds = sourceBounds;
this.destinationBounds = destinationBounds;
this.widthFactor = widthFactor;
this.heightFactor = heightFactor;
this.source = source;
this.destination = destination;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
int sourceX = this.sourceBounds.X;
int sourceY = this.sourceBounds.Y;
int destX = this.destinationBounds.X;
int destY = this.destinationBounds.Y;
int destLeft = this.destinationBounds.Left;
int destRight = this.destinationBounds.Right;
for (int y = rows.Min; y < rows.Max; y++)
{
// Y coordinates of source points
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY));
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y);
for (int x = destLeft; x < destRight; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)];
}
}
}
// Everything happens in BeforeImageApply.
}
}
}

13
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -19,7 +19,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// When sliding the window, the contents of the bottom window band are copied to the new top band.
/// For more details, and visual explanation, see "ResizeWorker.pptx".
/// </summary>
internal sealed class ResizeWorker<TPixel> : IDisposable
internal sealed class ResizeWorker<TResampler, TPixel> : IDisposable
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
private readonly Buffer2D<Vector4> transposedFirstPassBuffer;
@ -28,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly PixelConversionModifiers conversionModifiers;
private readonly ResizeKernelMap horizontalKernelMap;
private readonly ResizeKernelMap<TResampler> horizontalKernelMap;
private readonly BufferArea<TPixel> source;
@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly IMemoryOwner<Vector4> tempColumnBuffer;
private readonly ResizeKernelMap verticalKernelMap;
private readonly ResizeKernelMap<TResampler> verticalKernelMap;
private readonly int destWidth;
@ -56,8 +57,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration,
BufferArea<TPixel> source,
PixelConversionModifiers conversionModifiers,
ResizeKernelMap horizontalKernelMap,
ResizeKernelMap verticalKernelMap,
ResizeKernelMap<TResampler> horizontalKernelMap,
ResizeKernelMap<TResampler> verticalKernelMap,
int destWidth,
Rectangle targetWorkingRect,
Point targetOrigin)
@ -104,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.tempColumnBuffer.Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> GetColumnSpan(int x, int startY)
{
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);

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

@ -13,7 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
/// <summary>
/// Simplified reference implementation for <see cref="ResizeKernelMap"/> functionality.
/// </summary>
internal class ReferenceKernelMap
internal class ReferenceKernelMap<TResampler>
where TResampler : unmanaged, IResampler
{
private readonly ReferenceKernel[] kernels;
@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex];
public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true)
public static ReferenceKernelMap<TResampler> Calculate(TResampler sampler, int destinationSize, int sourceSize, bool normalize = true)
{
double ratio = (double)sourceSize / destinationSize;
double scale = ratio;
@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
result.Add(new ReferenceKernel(left, floatVals));
}
return new ReferenceKernelMap(result.ToArray());
return new ReferenceKernelMap<TResampler>(result.ToArray());
}
}

129
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs

@ -25,59 +25,60 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
/// <summary>
/// resamplerName, srcSize, destSize
/// </summary>
public static readonly TheoryData<string, int, int> KernelMapData = new TheoryData<string, int, int>
public static readonly TheoryData<IResampler, int, int> KernelMapData
= new TheoryData<IResampler, int, int>
{
{ nameof(KnownResamplers.Bicubic), 15, 10 },
{ nameof(KnownResamplers.Bicubic), 10, 15 },
{ nameof(KnownResamplers.Bicubic), 20, 20 },
{ nameof(KnownResamplers.Bicubic), 50, 40 },
{ nameof(KnownResamplers.Bicubic), 40, 50 },
{ nameof(KnownResamplers.Bicubic), 500, 200 },
{ nameof(KnownResamplers.Bicubic), 200, 500 },
{ nameof(KnownResamplers.Bicubic), 3032, 400 },
{ nameof(KnownResamplers.Bicubic), 10, 25 },
{ nameof(KnownResamplers.Lanczos3), 16, 12 },
{ nameof(KnownResamplers.Lanczos3), 12, 16 },
{ nameof(KnownResamplers.Lanczos3), 12, 9 },
{ nameof(KnownResamplers.Lanczos3), 9, 12 },
{ nameof(KnownResamplers.Lanczos3), 6, 8 },
{ nameof(KnownResamplers.Lanczos3), 8, 6 },
{ nameof(KnownResamplers.Lanczos3), 20, 12 },
{ 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 },
{ nameof(KnownResamplers.Lanczos8), 10, 100 },
{ 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:
{ nameof(KnownResamplers.Box), 378, 149 },
{ nameof(KnownResamplers.Box), 349, 174 },
{ KnownResamplers.Box, 378, 149 },
{ KnownResamplers.Box, 349, 174 },
// Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData
{ nameof(KnownResamplers.Box), 201, 100 },
{ nameof(KnownResamplers.Box), 199, 99 },
{ nameof(KnownResamplers.Box), 10, 299 },
{ nameof(KnownResamplers.Box), 299, 10 },
{ nameof(KnownResamplers.Box), 301, 300 },
{ nameof(KnownResamplers.Box), 1180, 480 },
{ nameof(KnownResamplers.Lanczos2), 3264, 3032 },
{ nameof(KnownResamplers.Bicubic), 1280, 2240 },
{ nameof(KnownResamplers.Bicubic), 1920, 1680 },
{ nameof(KnownResamplers.Bicubic), 3072, 2240 },
{ nameof(KnownResamplers.Welch), 300, 2008 },
{ 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
{ nameof(KnownResamplers.Bicubic), 10, 50 },
{ nameof(KnownResamplers.Bicubic), 49, 301 },
{ nameof(KnownResamplers.Bicubic), 301, 49 },
{ nameof(KnownResamplers.Bicubic), 1680, 1200 },
{ nameof(KnownResamplers.Box), 13, 299 },
{ nameof(KnownResamplers.Lanczos5), 3032, 600 },
{ KnownResamplers.Bicubic, 10, 50 },
{ KnownResamplers.Bicubic, 49, 301 },
{ KnownResamplers.Bicubic, 301, 49 },
{ KnownResamplers.Bicubic, 1680, 1200 },
{ KnownResamplers.Box, 13, 299 },
{ KnownResamplers.Lanczos5, 3032, 600 },
};
public static TheoryData<string, int, int> GeneratedImageResizeData =
@ -85,20 +86,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory(Skip = "Only for debugging and development")]
[MemberData(nameof(KernelMapData))]
public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize)
public void PrintNonNormalizedKernelMap<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : unmanaged, IResampler
{
IResampler resampler = TestUtils.GetResampler(resamplerName);
var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false);
var kernelMap = ReferenceKernelMap<TResampler>.Calculate(resampler, destSize, srcSize, false);
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
}
[Theory]
[MemberData(nameof(KernelMapData))]
public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize)
public void KernelMapContentIsCorrect<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : unmanaged, IResampler
{
this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize);
this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize);
}
// Comprehensive but expensive tests, for ResizeKernelMap.
@ -113,12 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
#endif
private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize)
private void VerifyKernelMapContentIsCorrect<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : unmanaged, IResampler
{
IResampler resampler = TestUtils.GetResampler(resamplerName);
var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize);
var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
var referenceMap = ReferenceKernelMap<TResampler>.Calculate(resampler, destSize, srcSize);
var kernelMap = ResizeKernelMap<TResampler>.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
#if DEBUG
this.Output.WriteLine(kernelMap.Info);
@ -153,20 +153,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
}
private static string PrintKernelMap(ResizeKernelMap kernelMap) =>
PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i));
private static string PrintKernelMap<TResampler>(ResizeKernelMap<TResampler> kernelMap)
where TResampler : unmanaged, IResampler
=> PrintKernelMap<TResampler, ResizeKernelMap<TResampler>>(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i));
private static string PrintKernelMap(ReferenceKernelMap kernelMap) =>
PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i));
private static string PrintKernelMap<TResampler>(ReferenceKernelMap<TResampler> kernelMap)
where TResampler : unmanaged, IResampler
=> PrintKernelMap<TResampler, ReferenceKernelMap<TResampler>>(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i));
private static string PrintKernelMap<TKernelMap>(
private static string PrintKernelMap<TResampler, TKernelMap>(
TKernelMap kernelMap,
Func<TKernelMap, int> getDestinationSize,
Func<TKernelMap, int, ReferenceKernel> getKernel)
where TResampler : unmanaged, IResampler
{
var bld = new StringBuilder();
if (kernelMap is ResizeKernelMap actualMap)
if (kernelMap is ResizeKernelMap<TResampler> actualMap)
{
bld.AppendLine(actualMap.Info);
}

4
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
configuration.MemoryAllocator = allocator;
configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes;
var verticalKernelMap = ResizeKernelMap.Calculate(
KnownResamplers.Bicubic,
var verticalKernelMap = ResizeKernelMap<BicubicResampler>.Calculate(
default,
destSize.Height,
image0.Height,
Configuration.Default.MemoryAllocator);

8
tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Pad(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
}
}
}

16
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
}
[Fact]
@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler, compand);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
}
@ -73,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(resizeOptions);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);

Loading…
Cancel
Save