diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index c0064d187..4e8284c2c 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -102,6 +102,15 @@ namespace SixLabors.ImageSharp
///
internal IFileSystem FileSystem { get; set; } = new LocalFileSystem();
+ ///
+ /// Gets or sets the working buffer size hint for image processors.
+ /// The default value is 1MB.
+ ///
+ ///
+ /// Currently only used by Resize.
+ ///
+ internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;
+
///
/// Gets or sets the image operations provider factory.
///
@@ -118,9 +127,9 @@ namespace SixLabors.ImageSharp
}
///
- /// Creates a shallow copy of the
+ /// Creates a shallow copy of the .
///
- /// A new configuration instance
+ /// A new configuration instance.
public Configuration Clone()
{
return new Configuration
@@ -130,18 +139,19 @@ namespace SixLabors.ImageSharp
MemoryAllocator = this.MemoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider,
ReadOrigin = this.ReadOrigin,
- FileSystem = this.FileSystem
+ FileSystem = this.FileSystem,
+ WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
};
}
///
/// Creates the default instance with the following s preregistered:
- ///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ /// .
///
- /// The default configuration of
+ /// The default configuration of .
internal static Configuration CreateDefaultInstance()
{
return new Configuration(
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index 17ab6e252..61fcb99db 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.Primitives;
@@ -14,55 +17,135 @@ namespace SixLabors.ImageSharp.Memory
internal static class Buffer2DExtensions
{
///
- /// Gets a to the backing buffer of .
+ /// Copy columns of inplace,
+ /// from positions starting at to positions at .
///
- internal static Span GetSpan(this Buffer2D buffer)
+ public static unsafe void CopyColumns(
+ this Buffer2D buffer,
+ int sourceIndex,
+ int destIndex,
+ int columnCount)
where T : struct
{
- return buffer.MemorySource.GetSpan();
+ DebugGuard.NotNull(buffer, nameof(buffer));
+ DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex));
+ DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex));
+ CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount);
+
+ int elementSize = Unsafe.SizeOf();
+ int width = buffer.Width * elementSize;
+ int sOffset = sourceIndex * elementSize;
+ int dOffset = destIndex * elementSize;
+ long count = columnCount * elementSize;
+
+ Span span = MemoryMarshal.AsBytes(buffer.Memory.Span);
+
+ fixed (byte* ptr = span)
+ {
+ byte* basePtr = (byte*)ptr;
+ for (int y = 0; y < buffer.Height; y++)
+ {
+ byte* sPtr = basePtr + sOffset;
+ byte* dPtr = basePtr + dOffset;
+
+ Buffer.MemoryCopy(sPtr, dPtr, count, count);
+
+ basePtr += width;
+ }
+ }
}
///
- /// Gets a to the row 'y' beginning from the pixel at 'x'.
+ /// Returns a representing the full area of the buffer.
+ ///
+ /// The element type
+ /// The
+ /// The
+ public static Rectangle FullRectangle(this Buffer2D buffer)
+ where T : struct
+ {
+ return new Rectangle(0, 0, buffer.Width, buffer.Height);
+ }
+
+ ///
+ /// Return a to the subarea represented by 'rectangle'
+ ///
+ /// The element type
+ /// The
+ /// The rectangle subarea
+ /// The
+ public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle)
+ where T : struct =>
+ new BufferArea(buffer, rectangle);
+
+ public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height)
+ where T : struct =>
+ new BufferArea(buffer, new Rectangle(x, y, width, height));
+
+ ///
+ /// Return a to the whole area of 'buffer'
+ ///
+ /// The element type
+ /// The
+ /// The
+ public static BufferArea GetArea(this Buffer2D buffer)
+ where T : struct =>
+ new BufferArea(buffer);
+
+ public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY)
+ where T : struct =>
+ new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
+
+ ///
+ /// Gets a span for all the pixels in defined by
+ ///
+ public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows)
+ where T : struct
+ {
+ return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
+ }
+
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
///
/// The buffer
- /// The x coordinate (position in the row)
/// The y (row) coordinate
/// The element type
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span GetRowSpan(this Buffer2D buffer, int x, int y)
+ public static Memory GetRowMemory(this Buffer2D buffer, int y)
where T : struct
{
- return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
+ return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
///
- /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ /// Gets a to the row 'y' beginning from the pixel at 'x'.
///
/// The buffer
+ /// The x coordinate (position in the row)
/// The y (row) coordinate
/// The element type
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span GetRowSpan(this Buffer2D buffer, int y)
+ public static Span GetRowSpan(this Buffer2D buffer, int x, int y)
where T : struct
{
- return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
+ return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
}
///
- /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
///
/// The buffer
/// The y (row) coordinate
/// The element type
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Memory GetRowMemory(this Buffer2D buffer, int y)
+ public static Span GetRowSpan(this Buffer2D buffer, int y)
where T : struct
{
- return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
+ return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
}
///
@@ -78,49 +161,28 @@ namespace SixLabors.ImageSharp.Memory
}
///
- /// Returns a representing the full area of the buffer.
+ /// Gets a to the backing buffer of .
///
- /// The element type
- /// The
- /// The
- public static Rectangle FullRectangle(this Buffer2D buffer)
+ internal static Span GetSpan(this Buffer2D buffer)
where T : struct
{
- return new Rectangle(0, 0, buffer.Width, buffer.Height);
+ return buffer.MemorySource.GetSpan();
}
- ///
- /// Return a to the subarea represented by 'rectangle'
- ///
- /// The element type
- /// The
- /// The rectangle subarea
- /// The
- public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle)
- where T : struct => new BufferArea(buffer, rectangle);
-
- public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height)
- where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height));
-
- public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY)
- where T : struct => new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
-
- ///
- /// Return a to the whole area of 'buffer'
- ///
- /// The element type
- /// The
- /// The
- public static BufferArea GetArea(this Buffer2D buffer)
- where T : struct => new BufferArea(buffer);
-
- ///
- /// Gets a span for all the pixels in defined by
- ///
- public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows)
+ [Conditional("DEBUG")]
+ private static void CheckColumnRegionsDoNotOverlap(
+ Buffer2D buffer,
+ int sourceIndex,
+ int destIndex,
+ int columnCount)
where T : struct
{
- return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
+ int minIndex = Math.Min(sourceIndex, destIndex);
+ int maxIndex = Math.Max(sourceIndex, destIndex);
+ if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount)
+ {
+ throw new InvalidOperationException("Column regions should not overlap!");
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs
index 835e880e9..815918754 100644
--- a/src/ImageSharp/Memory/RowInterval.cs
+++ b/src/ImageSharp/Memory/RowInterval.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Memory
@@ -8,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory
///
/// Represents an interval of rows in a and/or
///
- internal readonly struct RowInterval
+ internal readonly struct RowInterval : IEquatable
{
///
/// Initializes a new instance of the struct.
@@ -36,7 +38,33 @@ namespace SixLabors.ImageSharp.Memory
///
public int Height => this.Max - this.Min;
+ public static bool operator ==(RowInterval left, RowInterval right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(RowInterval left, RowInterval right)
+ {
+ return !left.Equals(right);
+ }
+
///
public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]";
+
+ public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max);
+
+ public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length);
+
+ public bool Equals(RowInterval other)
+ {
+ return this.Min == other.Min && this.Max == other.Max;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other);
+ }
+
+ public override int GetHashCode() => HashCode.Combine(this.Min, this.Max);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs
index bf77f8511..529041481 100644
--- a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs
+++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs
@@ -5,6 +5,9 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.PixelFormats
{
+ ///
+ /// Extension and utility methods for .
+ ///
internal static class PixelConversionModifiersExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -16,5 +19,20 @@ namespace SixLabors.ImageSharp.PixelFormats
this PixelConversionModifiers modifiers,
PixelConversionModifiers removeThis) =>
modifiers & ~removeThis;
+
+ ///
+ /// Applies the union of and ,
+ /// if is true, returns unmodified otherwise.
+ ///
+ ///
+ /// and
+ /// should be always used together!
+ ///
+ public static PixelConversionModifiers ApplyCompanding(
+ this PixelConversionModifiers originalModifiers,
+ bool compand) =>
+ compand
+ ? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand
+ : originalModifiers;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs
similarity index 88%
rename from src/ImageSharp/Processing/ResizeHelper.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs
index 3ae632162..956e6b84e 100644
--- a/src/ImageSharp/Processing/ResizeHelper.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs
@@ -1,11 +1,13 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
+using System.Numerics;
+
using SixLabors.Primitives;
-namespace SixLabors.ImageSharp.Processing
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
/// Provides methods to help calculate the target rectangle when resizing using the
@@ -13,6 +15,16 @@ namespace SixLabors.ImageSharp.Processing
///
internal static class ResizeHelper
{
+ public static unsafe int CalculateResizeWorkerHeightInWindowBands(
+ int windowBandHeight,
+ int width,
+ int sizeLimitHintInBytes)
+ {
+ int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4);
+ int sizeOfOneWindow = windowBandHeight * width;
+ return Math.Max(2, sizeLimitHint / sizeOfOneWindow);
+ }
+
///
/// Calculates the target location and bounds to perform the resize operation against.
///
@@ -21,9 +33,13 @@ namespace SixLabors.ImageSharp.Processing
/// The target width
/// The target height
///
- /// The .
+ /// The tuple representing the location and the bounds
///
- public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height)
+ public static (Size, Rectangle) CalculateTargetLocationAndBounds(
+ Size sourceSize,
+ ResizeOptions options,
+ int width,
+ int height)
{
switch (options.Mode)
{
@@ -44,7 +60,90 @@ namespace SixLabors.ImageSharp.Processing
}
}
- private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height)
+ private static (Size, Rectangle) CalculateBoxPadRectangle(
+ Size source,
+ ResizeOptions options,
+ int width,
+ int height)
+ {
+ if (width <= 0 || height <= 0)
+ {
+ return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
+ }
+
+ int sourceWidth = source.Width;
+ int sourceHeight = source.Height;
+
+ // Fractional variants for preserving aspect ratio.
+ float percentHeight = MathF.Abs(height / (float)sourceHeight);
+ float percentWidth = MathF.Abs(width / (float)sourceWidth);
+
+ int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth);
+ int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight);
+
+ // Only calculate if upscaling.
+ if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
+ {
+ int destinationX;
+ int destinationY;
+ int destinationWidth = sourceWidth;
+ int destinationHeight = sourceHeight;
+ width = boxPadWidth;
+ height = boxPadHeight;
+
+ switch (options.Position)
+ {
+ case AnchorPositionMode.Left:
+ destinationY = (height - sourceHeight) / 2;
+ destinationX = 0;
+ break;
+ case AnchorPositionMode.Right:
+ destinationY = (height - sourceHeight) / 2;
+ destinationX = width - sourceWidth;
+ break;
+ case AnchorPositionMode.TopRight:
+ destinationY = 0;
+ destinationX = width - sourceWidth;
+ break;
+ case AnchorPositionMode.Top:
+ destinationY = 0;
+ destinationX = (width - sourceWidth) / 2;
+ break;
+ case AnchorPositionMode.TopLeft:
+ destinationY = 0;
+ destinationX = 0;
+ break;
+ case AnchorPositionMode.BottomRight:
+ destinationY = height - sourceHeight;
+ destinationX = width - sourceWidth;
+ break;
+ case AnchorPositionMode.Bottom:
+ destinationY = height - sourceHeight;
+ destinationX = (width - sourceWidth) / 2;
+ break;
+ case AnchorPositionMode.BottomLeft:
+ destinationY = height - sourceHeight;
+ destinationX = 0;
+ break;
+ default:
+ destinationY = (height - sourceHeight) / 2;
+ destinationX = (width - sourceWidth) / 2;
+ break;
+ }
+
+ return (new Size(width, height),
+ new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
+ }
+
+ // Switch to pad mode to downscale and calculate from there.
+ return CalculatePadRectangle(source, options, width, height);
+ }
+
+ private static (Size, Rectangle) CalculateCropRectangle(
+ Size source,
+ ResizeOptions options,
+ int width,
+ int height)
{
if (width <= 0 || height <= 0)
{
@@ -147,152 +246,15 @@ namespace SixLabors.ImageSharp.Processing
destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
}
- return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
- }
-
- private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height)
- {
- if (width <= 0 || height <= 0)
- {
- return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
- }
-
- float ratio;
- int sourceWidth = source.Width;
- int sourceHeight = source.Height;
-
- int destinationX = 0;
- int destinationY = 0;
- int destinationWidth = width;
- int destinationHeight = height;
-
- // Fractional variants for preserving aspect ratio.
- float percentHeight = MathF.Abs(height / (float)sourceHeight);
- float percentWidth = MathF.Abs(width / (float)sourceWidth);
-
- if (percentHeight < percentWidth)
- {
- ratio = percentHeight;
- destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
-
- switch (options.Position)
- {
- case AnchorPositionMode.Left:
- case AnchorPositionMode.TopLeft:
- case AnchorPositionMode.BottomLeft:
- destinationX = 0;
- break;
- case AnchorPositionMode.Right:
- case AnchorPositionMode.TopRight:
- case AnchorPositionMode.BottomRight:
- destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
- break;
- default:
- destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
- break;
- }
- }
- else
- {
- ratio = percentWidth;
- destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
-
- switch (options.Position)
- {
- case AnchorPositionMode.Top:
- case AnchorPositionMode.TopLeft:
- case AnchorPositionMode.TopRight:
- destinationY = 0;
- break;
- case AnchorPositionMode.Bottom:
- case AnchorPositionMode.BottomLeft:
- case AnchorPositionMode.BottomRight:
- destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
- break;
- default:
- destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
- break;
- }
- }
-
- return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
- }
-
- private static (Size, Rectangle) CalculateBoxPadRectangle(Size source, ResizeOptions options, int width, int height)
- {
- if (width <= 0 || height <= 0)
- {
- return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
- }
-
- int sourceWidth = source.Width;
- int sourceHeight = source.Height;
-
- // Fractional variants for preserving aspect ratio.
- float percentHeight = MathF.Abs(height / (float)sourceHeight);
- float percentWidth = MathF.Abs(width / (float)sourceWidth);
-
- int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth);
- int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight);
-
- // Only calculate if upscaling.
- if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
- {
- int destinationX;
- int destinationY;
- int destinationWidth = sourceWidth;
- int destinationHeight = sourceHeight;
- width = boxPadWidth;
- height = boxPadHeight;
-
- switch (options.Position)
- {
- case AnchorPositionMode.Left:
- destinationY = (height - sourceHeight) / 2;
- destinationX = 0;
- break;
- case AnchorPositionMode.Right:
- destinationY = (height - sourceHeight) / 2;
- destinationX = width - sourceWidth;
- break;
- case AnchorPositionMode.TopRight:
- destinationY = 0;
- destinationX = width - sourceWidth;
- break;
- case AnchorPositionMode.Top:
- destinationY = 0;
- destinationX = (width - sourceWidth) / 2;
- break;
- case AnchorPositionMode.TopLeft:
- destinationY = 0;
- destinationX = 0;
- break;
- case AnchorPositionMode.BottomRight:
- destinationY = height - sourceHeight;
- destinationX = width - sourceWidth;
- break;
- case AnchorPositionMode.Bottom:
- destinationY = height - sourceHeight;
- destinationX = (width - sourceWidth) / 2;
- break;
- case AnchorPositionMode.BottomLeft:
- destinationY = height - sourceHeight;
- destinationX = 0;
- break;
- default:
- destinationY = (height - sourceHeight) / 2;
- destinationX = (width - sourceWidth) / 2;
- break;
- }
-
- return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
- }
-
- // Switch to pad mode to downscale and calculate from there.
- return CalculatePadRectangle(source, options, width, height);
+ return (new Size(width, height),
+ new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
}
- private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height)
+ private static (Size, Rectangle) CalculateMaxRectangle(
+ Size source,
+ ResizeOptions options,
+ int width,
+ int height)
{
int destinationWidth = width;
int destinationHeight = height;
@@ -320,7 +282,11 @@ namespace SixLabors.ImageSharp.Processing
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight));
}
- private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height)
+ private static (Size, Rectangle) CalculateMinRectangle(
+ Size source,
+ ResizeOptions options,
+ int width,
+ int height)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
@@ -372,5 +338,78 @@ namespace SixLabors.ImageSharp.Processing
// Replace the size to match the rectangle.
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight));
}
+
+ private static (Size, Rectangle) CalculatePadRectangle(
+ Size source,
+ ResizeOptions options,
+ int width,
+ int height)
+ {
+ if (width <= 0 || height <= 0)
+ {
+ return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
+ }
+
+ float ratio;
+ int sourceWidth = source.Width;
+ int sourceHeight = source.Height;
+
+ int destinationX = 0;
+ int destinationY = 0;
+ int destinationWidth = width;
+ int destinationHeight = height;
+
+ // Fractional variants for preserving aspect ratio.
+ float percentHeight = MathF.Abs(height / (float)sourceHeight);
+ float percentWidth = MathF.Abs(width / (float)sourceWidth);
+
+ if (percentHeight < percentWidth)
+ {
+ ratio = percentHeight;
+ destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
+
+ switch (options.Position)
+ {
+ case AnchorPositionMode.Left:
+ case AnchorPositionMode.TopLeft:
+ case AnchorPositionMode.BottomLeft:
+ destinationX = 0;
+ break;
+ case AnchorPositionMode.Right:
+ case AnchorPositionMode.TopRight:
+ case AnchorPositionMode.BottomRight:
+ destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
+ break;
+ default:
+ destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
+ break;
+ }
+ }
+ else
+ {
+ ratio = percentWidth;
+ destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
+
+ switch (options.Position)
+ {
+ case AnchorPositionMode.Top:
+ case AnchorPositionMode.TopLeft:
+ case AnchorPositionMode.TopRight:
+ destinationY = 0;
+ break;
+ case AnchorPositionMode.Bottom:
+ case AnchorPositionMode.BottomLeft:
+ case AnchorPositionMode.BottomRight:
+ destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
+ break;
+ default:
+ destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
+ break;
+ }
+ }
+
+ return (new Size(width, height),
+ new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
index f349634ac..dce4e70d6 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
@@ -19,27 +19,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Initializes a new instance of the struct.
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal ResizeKernel(int left, float* bufferPtr, int length)
+ internal ResizeKernel(int startIndex, float* bufferPtr, int length)
{
- this.Left = left;
+ this.StartIndex = startIndex;
this.bufferPtr = bufferPtr;
this.Length = length;
}
///
- /// Gets the left index for the destination row
+ /// Gets the start index for the destination row.
///
- public int Left { get; }
+ public int StartIndex { get; }
///
- /// Gets the the length of the kernel
+ /// Gets the the length of the kernel.
///
public int Length { get; }
///
- /// Gets the span representing the portion of the that this window covers
+ /// Gets the span representing the portion of the that this window covers.
///
- /// The
+ /// The .
///
public Span Values
{
@@ -54,10 +54,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The weighted sum
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span rowSpan)
+ {
+ return this.ConvolveCore(ref rowSpan[this.StartIndex]);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Vector4 ConvolveCore(ref Vector4 rowStartRef)
{
ref float horizontalValues = ref Unsafe.AsRef(this.bufferPtr);
- int left = this.Left;
- ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left);
// Destination color components
Vector4 result = Vector4.Zero;
@@ -65,7 +69,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int i = 0; i < this.Length; i++)
{
float weight = Unsafe.Add(ref horizontalValues, i);
- Vector4 v = Unsafe.Add(ref vecPtr, i);
+
+ // Vector4 v = offsetedRowSpan[i];
+ Vector4 v = Unsafe.Add(ref rowStartRef, i);
result += v * weight;
}
@@ -73,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
///
- /// Copy the contents of altering
+ /// Copy the contents of altering
/// to the value .
///
internal ResizeKernel AlterLeftValue(int left)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
index 2ab574df2..9abbb66e3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
@@ -54,11 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.radius = radius;
this.sourceLength = sourceLength;
this.DestinationLength = destinationLength;
- int maxWidth = (radius * 2) + 1;
- this.data = memoryAllocator.Allocate2D(maxWidth, bufferHeight, AllocationOptions.Clean);
+ this.MaxDiameter = (radius * 2) + 1;
+ this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean);
this.pinHandle = this.data.Memory.Pin();
this.kernels = new ResizeKernel[destinationLength];
- this.tempValues = new double[maxWidth];
+ this.tempValues = new double[this.MaxDiameter];
}
///
@@ -66,6 +66,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public int DestinationLength { get; }
+ ///
+ /// Gets the maximum diameter of the kernels.
+ ///
+ public int MaxDiameter { get; }
+
///
/// Gets a string of information to help debugging
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
index 21011ac71..e75f6014a 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
@@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.Sampler = options.Sampler;
this.Width = size.Width;
this.Height = size.Height;
- this.ResizeRectangle = rectangle;
+ this.TargetRectangle = rectangle;
this.Compand = options.Compand;
}
@@ -88,11 +89,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The target width.
/// The target height.
/// The source image size
- ///
+ ///
/// The structure that specifies the portion of the target image object to draw to.
///
/// Whether to compress or expand individual pixel color values on processing.
- public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle resizeRectangle, bool compand)
+ public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle targetRectangle, bool compand)
{
Guard.NotNull(sampler, nameof(sampler));
@@ -103,13 +104,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (width == 0 && height > 0)
{
width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height));
- resizeRectangle.Width = width;
+ targetRectangle.Width = width;
}
if (height == 0 && width > 0)
{
height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width));
- resizeRectangle.Height = height;
+ targetRectangle.Height = height;
}
Guard.MustBeGreaterThan(width, 0, nameof(width));
@@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.Sampler = sampler;
this.Width = width;
this.Height = height;
- this.ResizeRectangle = resizeRectangle;
+ this.TargetRectangle = targetRectangle;
this.Compand = compand;
}
@@ -140,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Gets the resize rectangle.
///
- public Rectangle ResizeRectangle { get; }
+ public Rectangle TargetRectangle { get; }
///
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
@@ -166,13 +167,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.Sampler,
- this.ResizeRectangle.Width,
+ this.TargetRectangle.Width,
sourceRectangle.Width,
memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate(
this.Sampler,
- this.ResizeRectangle.Height,
+ this.TargetRectangle.Height,
sourceRectangle.Height,
memoryAllocator);
}
@@ -182,7 +183,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
protected override void OnFrameApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration)
{
// Handle resize dimensions identical to the original
- if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.ResizeRectangle)
+ 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.GetPixelSpan().CopyTo(destination.GetPixelSpan());
@@ -193,26 +194,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int height = this.Height;
int sourceX = sourceRectangle.X;
int sourceY = sourceRectangle.Y;
- int startY = this.ResizeRectangle.Y;
- int endY = this.ResizeRectangle.Bottom;
- int startX = this.ResizeRectangle.X;
- int endX = this.ResizeRectangle.Right;
+ int startY = this.TargetRectangle.Y;
+ int startX = this.TargetRectangle.X;
- int minX = Math.Max(0, startX);
- int maxX = Math.Min(width, endX);
- int minY = Math.Max(0, startY);
- int maxY = Math.Min(height, endY);
+ var targetWorkingRect = Rectangle.Intersect(
+ this.TargetRectangle,
+ new Rectangle(0, 0, width, height));
if (this.Sampler is NearestNeighborResampler)
{
- var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
-
// Scaling factors
- float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width;
- float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height;
+ float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width;
+ float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height;
ParallelHelper.IterateRows(
- workingRect,
+ targetWorkingRect,
configuration,
rows =>
{
@@ -223,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span targetRow = destination.GetPixelRowSpan(y);
- for (int x = minX; x < maxX; x++)
+ for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
@@ -236,74 +232,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int sourceHeight = source.Height;
- PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply;
- if (this.Compand)
- {
- conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand;
- }
-
- // Interpolate the image using the calculated weights.
- // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
- // First process the columns. Since we are not using multiple threads startY and endY
- // are the upper and lower bounds of the source rectangle.
- using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(sourceHeight, width))
+ PixelConversionModifiers conversionModifiers =
+ PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand);
+
+ BufferArea 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(
+ configuration,
+ sourceArea,
+ conversionModifiers,
+ this.horizontalKernelMap,
+ this.verticalKernelMap,
+ width,
+ targetWorkingRect,
+ this.TargetRectangle.Location))
{
- firstPassPixelsTransposed.MemorySource.Clear();
-
- var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom);
-
- ParallelHelper.IterateRowsWithTempBuffer(
- processColsRect,
- configuration,
- (rows, tempRowBuffer) =>
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span sourceRow = source.GetPixelRowSpan(y).Slice(sourceX);
- Span tempRowSpan = tempRowBuffer.Span.Slice(sourceX);
-
- PixelOperations.Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers);
-
- ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y];
-
- for (int x = minX; x < maxX; x++)
- {
- ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX);
- Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan);
- }
- }
- });
-
- var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY);
-
- // Now process the rows.
- ParallelHelper.IterateRowsWithTempBuffer(
- processRowsRect,
- configuration,
- (rows, tempRowBuffer) =>
- {
- Span tempRowSpan = tempRowBuffer.Span;
-
- for (int y = rows.Min; y < rows.Max; y++)
- {
- // Ensure offsets are normalized for cropping and padding.
- ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY);
+ worker.Initialize();
- ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan);
-
- for (int x = 0; x < width; x++)
- {
- Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY);
-
- // Destination color components
- Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn);
- }
-
- Span targetRowSpan = destination.GetPixelRowSpan(y);
-
- PixelOperations.Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers);
- }
- });
+ var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom);
+ worker.FillDestinationPixels(workingInterval, destination.PixelBuffer);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
new file mode 100644
index 000000000..00a8cfbf3
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
@@ -0,0 +1,193 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Implements the resize algorithm using a sliding window of size
+ /// maximized by .
+ /// The height of the window is a multiple of the vertical kernel's maximum diameter.
+ /// 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".
+ ///
+ internal class ResizeWorker : IDisposable
+ where TPixel : struct, IPixel
+ {
+ private readonly Buffer2D transposedFirstPassBuffer;
+
+ private readonly Configuration configuration;
+
+ private readonly PixelConversionModifiers conversionModifiers;
+
+ private readonly ResizeKernelMap horizontalKernelMap;
+
+ private readonly BufferArea source;
+
+ private readonly Rectangle sourceRectangle;
+
+ private readonly IMemoryOwner tempRowBuffer;
+
+ private readonly IMemoryOwner tempColumnBuffer;
+
+ private readonly ResizeKernelMap verticalKernelMap;
+
+ private readonly int destWidth;
+
+ private readonly Rectangle targetWorkingRect;
+
+ private readonly Point targetOrigin;
+
+ private readonly int windowBandHeight;
+
+ private readonly int workerHeight;
+
+ private RowInterval currentWindow;
+
+ public ResizeWorker(
+ Configuration configuration,
+ BufferArea source,
+ PixelConversionModifiers conversionModifiers,
+ ResizeKernelMap horizontalKernelMap,
+ ResizeKernelMap verticalKernelMap,
+ int destWidth,
+ Rectangle targetWorkingRect,
+ Point targetOrigin)
+ {
+ this.configuration = configuration;
+ this.source = source;
+ this.sourceRectangle = source.Rectangle;
+ this.conversionModifiers = conversionModifiers;
+ this.horizontalKernelMap = horizontalKernelMap;
+ this.verticalKernelMap = verticalKernelMap;
+ this.destWidth = destWidth;
+ this.targetWorkingRect = targetWorkingRect;
+ this.targetOrigin = targetOrigin;
+
+ this.windowBandHeight = verticalKernelMap.MaxDiameter;
+
+ int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(
+ this.windowBandHeight,
+ destWidth,
+ configuration.WorkingBufferSizeHintInBytes);
+
+ this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight);
+
+ this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D(
+ this.workerHeight,
+ destWidth,
+ AllocationOptions.Clean);
+
+ this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width);
+ this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(destWidth);
+
+ this.currentWindow = new RowInterval(0, this.workerHeight);
+ }
+
+ public void Dispose()
+ {
+ this.transposedFirstPassBuffer.Dispose();
+ this.tempRowBuffer.Dispose();
+ this.tempColumnBuffer.Dispose();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetColumnSpan(int x, int startY)
+ {
+ return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
+ }
+
+ public void Initialize()
+ {
+ this.CalculateFirstPassValues(this.currentWindow);
+ }
+
+ public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination)
+ {
+ Span tempColSpan = this.tempColumnBuffer.GetSpan();
+
+ for (int y = rowInterval.Min; y < rowInterval.Max; y++)
+ {
+ // Ensure offsets are normalized for cropping and padding.
+ ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y);
+
+ if (kernel.StartIndex + kernel.Length > this.currentWindow.Max)
+ {
+ this.Slide();
+ }
+
+ ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan);
+
+ int top = kernel.StartIndex - this.currentWindow.Min;
+
+ ref Vector4 fpBase = ref this.transposedFirstPassBuffer.Span[top];
+
+ for (int x = 0; x < this.destWidth; x++)
+ {
+ ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight);
+
+ // Destination color components
+ Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase);
+ }
+
+ Span targetRowSpan = destination.GetRowSpan(y);
+
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers);
+ }
+ }
+
+ private void Slide()
+ {
+ int minY = this.currentWindow.Max - this.windowBandHeight;
+ int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height);
+
+ // Copy previous bottom band to the new top:
+ // (rows <--> columns, because the buffer is transposed)
+ this.transposedFirstPassBuffer.CopyColumns(
+ this.workerHeight - this.windowBandHeight,
+ 0,
+ this.windowBandHeight);
+
+ this.currentWindow = new RowInterval(minY, maxY);
+
+ // Calculate the remainder:
+ this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight));
+ }
+
+ private void CalculateFirstPassValues(RowInterval calculationInterval)
+ {
+ Span tempRowSpan = this.tempRowBuffer.GetSpan();
+ for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
+ {
+ Span sourceRow = this.source.GetRowSpan(y);
+
+ PixelOperations.Instance.ToVector4(
+ this.configuration,
+ sourceRow,
+ tempRowSpan,
+ this.conversionModifiers);
+
+ // Span firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min);
+ ref Vector4 firstPassBaseRef = ref this.transposedFirstPassBuffer.Span[y - this.currentWindow.Min];
+
+ for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++)
+ {
+ ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X);
+
+ // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan);
+ Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx
new file mode 100644
index 000000000..248959170
Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.pptx differ
diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
deleted file mode 100644
index 41c9ab6c7..000000000
--- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-using BenchmarkDotNet.Attributes;
-
-namespace SixLabors.ImageSharp.Benchmarks.General
-{
- [Config(typeof(Config.ShortClr))]
- public class ArrayCopy
- {
- [Params(10, 100, 1000, 10000)]
- public int Count { get; set; }
-
- byte[] source;
-
- byte[] destination;
-
- [GlobalSetup]
- public void SetUp()
- {
- this.source = new byte[this.Count];
- this.destination = new byte[this.Count];
- }
-
- [Benchmark(Baseline = true, Description = "Copy using Array.Copy()")]
- public void CopyArray()
- {
- Array.Copy(this.source, this.destination, this.Count);
- }
-
- [Benchmark(Description = "Copy using Unsafe")]
- public unsafe void CopyUnsafe()
- {
- fixed (byte* pinnedDestination = this.destination)
- fixed (byte* pinnedSource = this.source)
- {
- Unsafe.CopyBlock(pinnedSource, pinnedDestination, (uint)this.Count);
- }
- }
-
- [Benchmark(Description = "Copy using Buffer.BlockCopy()")]
- public void CopyUsingBufferBlockCopy()
- {
- Buffer.BlockCopy(this.source, 0, this.destination, 0, this.Count);
- }
-
- [Benchmark(Description = "Copy using Buffer.MemoryCopy")]
- public unsafe void CopyUsingBufferMemoryCopy()
- {
- fixed (byte* pinnedDestination = this.destination)
- fixed (byte* pinnedSource = this.source)
- {
- Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count);
- }
- }
-
- [Benchmark(Description = "Copy using Marshal.Copy")]
- public unsafe void CopyUsingMarshalCopy()
- {
- fixed (byte* pinnedDestination = this.destination)
- {
- Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count);
- }
- }
-
- /*****************************************************************************************************************
- *************** RESULTS on i7-4810MQ 2.80GHz + Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1085.0 ********************
- *****************************************************************************************************************
- *
- * Method | Count | Mean | StdErr | StdDev | Scaled | Scaled-StdDev |
- * ---------------------------------- |------ |------------ |----------- |----------- |------- |-------------- |
- * 'Copy using Array.Copy()' | 10 | 20.3074 ns | 0.1194 ns | 0.2068 ns | 1.00 | 0.00 |
- * 'Copy using Unsafe' | 10 | 6.1002 ns | 0.1981 ns | 0.3432 ns | 0.30 | 0.01 |
- * 'Copy using Buffer.BlockCopy()' | 10 | 10.7879 ns | 0.0984 ns | 0.1705 ns | 0.53 | 0.01 |
- * 'Copy using Buffer.MemoryCopy' | 10 | 4.9625 ns | 0.0200 ns | 0.0347 ns | 0.24 | 0.00 |
- * 'Copy using Marshal.Copy' | 10 | 16.1782 ns | 0.0919 ns | 0.1592 ns | 0.80 | 0.01 |
- *
- * 'Copy using Array.Copy()' | 100 | 31.5945 ns | 0.2908 ns | 0.5037 ns | 1.00 | 0.00 |
- * 'Copy using Unsafe' | 100 | 10.2722 ns | 0.5202 ns | 0.9010 ns | 0.33 | 0.02 |
- * 'Copy using Buffer.BlockCopy()' | 100 | 22.0322 ns | 0.0284 ns | 0.0493 ns | 0.70 | 0.01 |
- * 'Copy using Buffer.MemoryCopy' | 100 | 10.2472 ns | 0.0359 ns | 0.0622 ns | 0.32 | 0.00 |
- * 'Copy using Marshal.Copy' | 100 | 34.3820 ns | 1.1868 ns | 2.0555 ns | 1.09 | 0.05 |
- *
- * 'Copy using Array.Copy()' | 1000 | 40.9743 ns | 0.0521 ns | 0.0902 ns | 1.00 | 0.00 |
- * 'Copy using Unsafe' | 1000 | 42.7840 ns | 2.0139 ns | 3.4882 ns | 1.04 | 0.07 |
- * 'Copy using Buffer.BlockCopy()' | 1000 | 33.7361 ns | 0.0751 ns | 0.1300 ns | 0.82 | 0.00 |
- * 'Copy using Buffer.MemoryCopy' | 1000 | 35.7541 ns | 0.0480 ns | 0.0832 ns | 0.87 | 0.00 |
- * 'Copy using Marshal.Copy' | 1000 | 42.2028 ns | 0.2769 ns | 0.4795 ns | 1.03 | 0.01 |
- *
- * 'Copy using Array.Copy()' | 10000 | 200.0438 ns | 0.2251 ns | 0.3899 ns | 1.00 | 0.00 |
- * 'Copy using Unsafe' | 10000 | 389.6957 ns | 13.2770 ns | 22.9964 ns | 1.95 | 0.09 |
- * 'Copy using Buffer.BlockCopy()' | 10000 | 191.3478 ns | 0.1557 ns | 0.2697 ns | 0.96 | 0.00 |
- * 'Copy using Buffer.MemoryCopy' | 10000 | 196.4679 ns | 0.2731 ns | 0.4730 ns | 0.98 | 0.00 |
- * 'Copy using Marshal.Copy' | 10000 | 202.5392 ns | 0.5561 ns | 0.9631 ns | 1.01 | 0.00 |
- *
- */
- }
-}
diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs
new file mode 100644
index 000000000..32f1d10c7
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs
@@ -0,0 +1,231 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using BenchmarkDotNet.Attributes;
+
+namespace SixLabors.ImageSharp.Benchmarks.General
+{
+ ///
+ /// Compare different methods for copying native and/or managed buffers.
+ /// Conclusions:
+ /// - Span.CopyTo() has terrible performance on classic .NET Framework
+ /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning)
+ ///
+ [Config(typeof(Config.ShortClr))]
+ public class CopyBuffers
+ {
+ private byte[] destArray;
+
+ private MemoryHandle destHandle;
+
+ private Memory destMemory;
+
+ private byte[] sourceArray;
+
+ private MemoryHandle sourceHandle;
+
+ private Memory sourceMemory;
+
+ [Params(10, 50, 100, 1000, 10000)]
+ public int Count { get; set; }
+
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ this.sourceArray = new byte[this.Count];
+ this.sourceMemory = new Memory(this.sourceArray);
+ this.sourceHandle = this.sourceMemory.Pin();
+
+ this.destArray = new byte[this.Count];
+ this.destMemory = new Memory(this.destArray);
+ this.destHandle = this.destMemory.Pin();
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ this.sourceHandle.Dispose();
+ this.destHandle.Dispose();
+ }
+
+ [Benchmark(Baseline = true, Description = "Array.Copy()")]
+ public void ArrayCopy()
+ {
+ Array.Copy(this.sourceArray, this.destArray, this.Count);
+ }
+
+ [Benchmark(Description = "Buffer.BlockCopy()")]
+ public void BufferBlockCopy()
+ {
+ Buffer.BlockCopy(this.sourceArray, 0, this.destArray, 0, this.Count);
+ }
+
+ [Benchmark(Description = "Buffer.MemoryCopy()")]
+ public unsafe void BufferMemoryCopy()
+ {
+ void* pinnedDestination = this.destHandle.Pointer;
+ void* pinnedSource = this.sourceHandle.Pointer;
+ Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count);
+ }
+
+
+ [Benchmark(Description = "Marshal.Copy()")]
+ public unsafe void MarshalCopy()
+ {
+ void* pinnedDestination = this.destHandle.Pointer;
+ Marshal.Copy(this.sourceArray, 0, (IntPtr)pinnedDestination, this.Count);
+ }
+
+ [Benchmark(Description = "Span.CopyTo()")]
+ public void SpanCopyTo()
+ {
+ this.sourceMemory.Span.CopyTo(this.destMemory.Span);
+ }
+
+ [Benchmark(Description = "Unsafe.CopyBlock(ref)")]
+ public unsafe void UnsafeCopyBlockReferences()
+ {
+ Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count);
+ }
+
+ [Benchmark(Description = "Unsafe.CopyBlock(ptr)")]
+ public unsafe void UnsafeCopyBlockPointers()
+ {
+ void* pinnedDestination = this.destHandle.Pointer;
+ void* pinnedSource = this.sourceHandle.Pointer;
+ Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count);
+ }
+
+ [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")]
+ public unsafe void UnsafeCopyBlockUnalignedReferences()
+ {
+ Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count);
+ }
+
+ [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")]
+ public unsafe void UnsafeCopyBlockUnalignedPointers()
+ {
+ void* pinnedDestination = this.destHandle.Pointer;
+ void* pinnedSource = this.sourceHandle.Pointer;
+ Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count);
+ }
+
+ // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
+ // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
+ // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC
+ // .NET Core SDK=2.2.202
+ // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0
+ // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ //
+ // IterationCount=3 LaunchCount=1 WarmupCount=3
+ //
+ // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:|
+ // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - |
+ // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - |
+ // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - |
+ // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - |
+ // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - |
+ // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - |
+ // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - |
+ // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - |
+ // | | | | | | | | | | | | | |
+ // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - |
+ // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - |
+ // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - |
+ // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - |
+ // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - |
+ // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - |
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
index e99163f8b..cf47202cc 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
@@ -3,6 +3,7 @@
using System.Drawing;
using System.Drawing.Drawing2D;
+using System.Globalization;
using BenchmarkDotNet.Attributes;
@@ -22,15 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks
private Bitmap sourceBitmap;
- [Params(3032)]
- public int SourceSize { get; set; }
+ [Params("3032-400")]
+ public virtual string SourceToDest { get; set; }
+
+ protected int SourceSize { get; private set; }
+
+ protected int DestSize { get; private set; }
- [Params(400)]
- public int DestSize { get; set; }
[GlobalSetup]
- public void Setup()
+ public virtual void Setup()
{
+ string[] stuff = this.SourceToDest.Split('-');
+ this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture);
+ this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture);
+
this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize);
this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize);
}
@@ -94,26 +101,44 @@ namespace SixLabors.ImageSharp.Benchmarks
ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
}
- // RESULTS (2019 April):
+ // RESULTS - 2019 April - ResizeWorker:
//
- // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4)
+ // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4)
// Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
- // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC
- // .NET Core SDK=2.1.602
+ // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC
+ // .NET Core SDK=2.2.202
// [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
- // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0
+ // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0
// Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
//
- // IterationCount=3 LaunchCount=1 WarmupCount=3
+ // IterationCount=3 LaunchCount=1 WarmupCount=3
//
- // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
- // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:|
- // SystemDrawing | Clr | Clr | 3032 | 400 | 118.71 ms | 4.884 ms | 0.2677 ms | 1.00 | - | - | - | 2048 B |
- // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 94.55 ms | 16.160 ms | 0.8858 ms | 0.80 | - | - | - | 16384 B |
- // | | | | | | | | | | | | |
- // SystemDrawing | Core | Core | 3032 | 400 | 118.38 ms | 2.814 ms | 0.1542 ms | 1.00 | - | - | - | 96 B |
- // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 90.28 ms | 4.679 ms | 0.2565 ms | 0.76 | - | - | - | 15712 B |
+ // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
+ // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:|
+ // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B |
+ // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B |
+ // | | | | | | | | | | | | |
+ // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B |
+ // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B |
+ }
+
+ ///
+ /// Is it worth to set a larger working buffer limit for resize?
+ /// Conclusion: It doesn't really have an effect.
+ ///
+ public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32
+ {
+ [Params(128, 512, 1024, 8 * 1024)]
+ public int WorkingBufferSizeHintInKilobytes { get; set; }
+
+ [Params("3032-400", "4000-300")]
+ public override string SourceToDest { get; set; }
+ public override void Setup()
+ {
+ this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024;
+ base.Setup();
+ }
}
public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase
diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
index cb286cc28..6569dc002 100644
--- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
+++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
@@ -1,7 +1,7 @@
Exe
- net461
+ net472
win7-x64
True
false
diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs
index 02d4f80c5..afe7eb04f 100644
--- a/tests/ImageSharp.Sandbox46/Program.cs
+++ b/tests/ImageSharp.Sandbox46/Program.cs
@@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.Sandbox46
///
public static void Main(string[] args)
{
- RunJpegColorProfilingTests();
+ // RunJpegColorProfilingTests();
// RunDecodeJpegProfilingTests();
// RunToVector4ProfilingTest();
- // RunResizeProfilingTest();
+ RunResizeProfilingTest();
Console.ReadLine();
}
@@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Sandbox46
private static void RunResizeProfilingTest()
{
var test = new ResizeProfilingBenchmarks(new ConsoleOutput());
- test.ResizeBicubic(2000, 2000);
+ test.ResizeBicubic(4000, 4000);
}
private static void RunToVector4ProfilingTest()
diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs
index 208387e6d..6f68d0428 100644
--- a/tests/ImageSharp.Tests/ConfigurationTests.cs
+++ b/tests/ImageSharp.Tests/ConfigurationTests.cs
@@ -39,13 +39,13 @@ namespace SixLabors.ImageSharp.Tests
/// Test that the default configuration is not null.
///
[Fact]
- public void TestDefaultConfigurationIsNotNull() => Assert.True(Configuration.Default != null);
+ public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null);
///
/// Test that the default configuration read origin options is set to begin.
///
[Fact]
- public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current);
+ public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current);
///
/// Test that the default configuration parallel options max degrees of parallelism matches the
@@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void TestDefaultConfigurationMaxDegreeOfParallelism()
{
- Assert.True(Configuration.Default.MaxDegreeOfParallelism == Environment.ProcessorCount);
+ Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount);
var cfg = new Configuration();
Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount);
@@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests
public void ConfigurationCannotAddDuplicates()
{
const int count = 4;
- Configuration config = Configuration.Default;
+ Configuration config = this.DefaultConfiguration;
Assert.Equal(count, config.ImageFormats.Count());
@@ -105,9 +105,16 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void DefaultConfigurationHasCorrectFormatCount()
{
- Configuration config = Configuration.Default;
+ Configuration config = Configuration.CreateDefaultInstance();
Assert.Equal(4, config.ImageFormats.Count());
}
+
+ [Fact]
+ public void WorkingBufferSizeHint_DefaultIsCorrect()
+ {
+ Configuration config = this.DefaultConfiguration;
+ Assert.True(config.WorkingBufferSizeHintInBytes > 1024);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
index 629b3cdeb..3aead6aaa 100644
--- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
@@ -34,5 +34,54 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.True(Unsafe.AreSame(ref expected0, ref actual0));
}
}
+
+ [Fact]
+ public void Slice1()
+ {
+ RowInterval rowInterval = new RowInterval(10, 20);
+ RowInterval sliced = rowInterval.Slice(5);
+
+ Assert.Equal(15, sliced.Min);
+ Assert.Equal(20, sliced.Max);
+ }
+
+ [Fact]
+ public void Slice2()
+ {
+ RowInterval rowInterval = new RowInterval(10, 20);
+ RowInterval sliced = rowInterval.Slice(3, 5);
+
+ Assert.Equal(13, sliced.Min);
+ Assert.Equal(18, sliced.Max);
+ }
+
+ [Fact]
+ public void Equality_WhenTrue()
+ {
+ RowInterval a = new RowInterval(42, 123);
+ RowInterval b = new RowInterval(42, 123);
+
+ Assert.True(a.Equals(b));
+ Assert.True(a.Equals((object)b));
+ Assert.True(a == b);
+ Assert.Equal(a.GetHashCode(), b.GetHashCode());
+ }
+
+ [Fact]
+ public void Equality_WhenFalse()
+ {
+ RowInterval a = new RowInterval(42, 123);
+ RowInterval b = new RowInterval(42, 125);
+ RowInterval c = new RowInterval(40, 123);
+
+ Assert.False(a.Equals(b));
+ Assert.False(c.Equals(a));
+ Assert.False(b.Equals(c));
+
+ Assert.False(a.Equals((object)b));
+ Assert.False(a.Equals(null));
+ Assert.False(a == b);
+ Assert.True(a != c);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
index 19ec725f2..4af3b81e2 100644
--- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
+++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
@@ -127,5 +127,56 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(new Size(10, 5), b.Size());
}
}
+
+ [Theory]
+ [InlineData(100, 20, 0, 90, 10)]
+ [InlineData(100, 3, 0, 50, 50)]
+ [InlineData(123, 23, 10, 80, 13)]
+ [InlineData(10, 1, 3, 6, 3)]
+ [InlineData(2, 2, 0, 1, 1)]
+ [InlineData(5, 1, 1, 3, 2)]
+ public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount)
+ {
+ Random rnd = new Random(123);
+ using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height))
+ {
+ rnd.RandomFill(b.Span, 0, 1);
+
+ b.CopyColumns(startIndex, destIndex, columnCount);
+
+ for (int y = 0; y < b.Height; y++)
+ {
+ Span row = b.GetRowSpan(y);
+
+ Span s = row.Slice(startIndex, columnCount);
+ Span d = row.Slice(destIndex, columnCount);
+
+ Xunit.Assert.True(s.SequenceEqual(d));
+ }
+ }
+ }
+
+ [Fact]
+ public void CopyColumns_InvokeMultipleTimes()
+ {
+ Random rnd = new Random(123);
+ using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100))
+ {
+ rnd.RandomFill(b.Span, 0, 1);
+
+ b.CopyColumns(0, 50, 22);
+ b.CopyColumns(0, 50, 22);
+
+ for (int y = 0; y < b.Height; y++)
+ {
+ Span row = b.GetRowSpan(y);
+
+ Span s = row.Slice(0, 22);
+ Span d = row.Slice(50, 22);
+
+ Xunit.Assert.True(s.SequenceEqual(d));
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs
new file mode 100644
index 000000000..e98e14fc6
--- /dev/null
+++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs
@@ -0,0 +1,65 @@
+// // Copyright (c) Six Labors and contributors.
+// // Licensed under the Apache License, Version 2.0.
+// // Copyright (c) Six Labors and contributors.
+// // Licensed under the Apache License, Version 2.0.
+// // Copyright (c) Six Labors and contributors.
+// // Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
+{
+ public class PixelConversionModifiersExtensionsTests
+ {
+ [Theory]
+ [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.None, true)]
+ [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, false)]
+ [InlineData(PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Premultiply, false)]
+ [InlineData(
+ PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale,
+ PixelConversionModifiers.Premultiply,
+ true)]
+ [InlineData(
+ PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale,
+ PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale,
+ true)]
+ [InlineData(
+ PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale,
+ PixelConversionModifiers.Scale,
+ true)]
+ internal void IsDefined(
+ PixelConversionModifiers baselineModifiers,
+ PixelConversionModifiers checkModifiers,
+ bool expected)
+ {
+ Assert.Equal(expected, baselineModifiers.IsDefined(checkModifiers));
+ }
+
+ [Theory]
+ [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand,
+ PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)]
+ [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, PixelConversionModifiers.None)]
+ internal void Remove(
+ PixelConversionModifiers baselineModifiers,
+ PixelConversionModifiers toRemove,
+ PixelConversionModifiers expected)
+ {
+ PixelConversionModifiers result = baselineModifiers.Remove(toRemove);
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [InlineData(PixelConversionModifiers.Premultiply, false, PixelConversionModifiers.Premultiply)]
+ [InlineData(PixelConversionModifiers.Premultiply, true, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)]
+ internal void ApplyCompanding(
+ PixelConversionModifiers baselineModifiers,
+ bool compand,
+ PixelConversionModifiers expected)
+ {
+ PixelConversionModifiers result = baselineModifiers.ApplyCompanding(compand);
+ Assert.Equal(expected, result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
index de72f6d09..edb24d6f1 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
@@ -42,6 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2);
ctx.DetectEdges(bounds);
},
+ comparer: ValidatorComparer,
useReferenceOutputFrom: nameof(this.DetectEdges_InBox));
}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs
new file mode 100644
index 000000000..b7b4597c7
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
+{
+ public class ResamplerTests
+ {
+ [Theory]
+ [InlineData(-2, 0)]
+ [InlineData(-1, 0)]
+ [InlineData(0, 1)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ public static void BicubicWindowOscillatesCorrectly(float x, float expected)
+ {
+ IResampler sampler = KnownResamplers.Bicubic;
+ float result = sampler.GetValue(x);
+
+ Assert.Equal(result, expected);
+ }
+
+ [Theory]
+ [InlineData(-2, 0)]
+ [InlineData(-1, 0)]
+ [InlineData(0, 1)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
+ {
+ IResampler sampler = KnownResamplers.Lanczos3;
+ float result = sampler.GetValue(x);
+
+ Assert.Equal(result, expected);
+ }
+
+ [Theory]
+ [InlineData(-4, 0)]
+ [InlineData(-2, 0)]
+ [InlineData(0, 1)]
+ [InlineData(2, 0)]
+ [InlineData(4, 0)]
+ public static void Lanczos5WindowOscillatesCorrectly(float x, float expected)
+ {
+ IResampler sampler = KnownResamplers.Lanczos5;
+ float result = sampler.GetValue(x);
+
+ Assert.Equal(result, expected);
+ }
+
+ [Theory]
+ [InlineData(-2, 0)]
+ [InlineData(-1, 0)]
+ [InlineData(0, 1)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ public static void TriangleWindowOscillatesCorrectly(float x, float expected)
+ {
+ IResampler sampler = KnownResamplers.Triangle;
+ float result = sampler.GetValue(x);
+
+ Assert.Equal(result, expected);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs
new file mode 100644
index 000000000..b0d8ef653
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
+{
+ public class ResizeHelperTests
+ {
+
+ [Theory]
+ [InlineData(20, 100, 1, 2)]
+ [InlineData(20, 100, 20*100*16, 2)]
+ [InlineData(20, 100, 40*100*16, 2)]
+ [InlineData(20, 100, 59*100*16, 2)]
+ [InlineData(20, 100, 60*100*16, 3)]
+ [InlineData(17, 63, 5*17*63*16, 5)]
+ [InlineData(17, 63, 5*17*63*16+1, 5)]
+ [InlineData(17, 63, 6*17*63*16-1, 5)]
+ [InlineData(33, 400, 1*1024*1024, 4)]
+ [InlineData(33, 400, 8*1024*1024, 39)]
+ [InlineData(50, 300, 1*1024*1024, 4)]
+ public void CalculateResizeWorkerHeightInWindowBands(
+ int windowDiameter,
+ int width,
+ int sizeLimitHintInBytes,
+ int expectedCount)
+ {
+ int actualCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(windowDiameter, width, sizeLimitHintInBytes);
+ Assert.Equal(expectedCount, actualCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
index 7d842c4e1..2c5914253 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
@@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public static implicit operator ReferenceKernel(ResizeKernel orig)
{
- return new ReferenceKernel(orig.Left, orig.Values.ToArray());
+ return new ReferenceKernel(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 5d3790f07..51680eee0 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
@@ -36,6 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ 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 },
@@ -93,7 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
GenerateImageResizeData();
- [Theory(Skip = "Only for debugging and development")]
+ [Theory(
+ Skip = "Only for debugging and development"
+ )]
[MemberData(nameof(KernelMapData))]
public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize)
{
@@ -130,7 +133,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize);
var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
+
+
#if DEBUG
+ this.Output.WriteLine(kernelMap.Info);
this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n");
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
#endif
@@ -146,8 +152,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
referenceKernel.Length == kernel.Length,
$"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}");
Assert.True(
- referenceKernel.Left == kernel.Left,
- $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.Left}");
+ referenceKernel.Left == kernel.StartIndex,
+ $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}");
float[] expectedValues = referenceKernel.Values;
Span actualValues = kernel.Values;
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index 034b66ae9..e3a43a652 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -2,133 +2,190 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.ImageSharp.Tests.Memory;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+using SixLabors.Memory;
using SixLabors.Primitives;
using Xunit;
+
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
- public class ResizeTests : FileTestBase
+ public class ResizeTests
{
- public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
+ private const PixelTypes CommonNonDefaultPixelTypes =
+ PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
- private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F);
+ private const PixelTypes DefaultPixelType = PixelTypes.Rgba32;
public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames();
+ public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
+
public static readonly string[] SmokeTestResamplerNames =
{
- nameof(KnownResamplers.NearestNeighbor),
- nameof(KnownResamplers.Bicubic),
+ nameof(KnownResamplers.NearestNeighbor),
+ nameof(KnownResamplers.Bicubic),
nameof(KnownResamplers.Box),
nameof(KnownResamplers.Lanczos5),
};
- [Theory]
- [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), DefaultPixelType, 0.5f, null, null)]
- [WithFileCollection(nameof(CommonTestImages), nameof(SmokeTestResamplerNames), DefaultPixelType, 0.3f, null, null)]
- [WithFileCollection(nameof(CommonTestImages), nameof(SmokeTestResamplerNames), DefaultPixelType, 1.8f, null, null)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 0.5f, null, null)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 1f, null, null)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, DefaultPixelType, 8f, null, null)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, DefaultPixelType, null, 100, 99)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, DefaultPixelType, null, 300, 480)]
- [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, DefaultPixelType, null, 301, 100)]
- public void Resize_WorksWithAllResamplers(
- TestImageProvider provider,
- string samplerName,
- float? ratio,
- int? specificDestWidth,
- int? specificDestHeight)
+ private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F);
+
+ [Theory(
+ Skip = "Debug only, enable manually"
+ )]
+ [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)]
+ [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 1024)]
+ [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 128)]
+ public void LargeImage(TestImageProvider provider, int destSize, int workingBufferSizeHintInKilobytes)
where TPixel : struct, IPixel
{
- IResampler sampler = TestUtils.GetResampler(samplerName);
+ if (!TestEnvironment.Is64BitProcess)
+ {
+ return;
+ }
- // NeirestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit
- // most likely because of differences in numeric behavior.
- // The difference is well visible when comparing output for
- // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png
- // TODO: Should we investigate this?
- bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess
- && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion)
- && sampler is NearestNeighborResampler;
+ provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024;
- var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f);
+ using (var image = provider.GetImage())
+ {
+ image.Mutate(x => x.Resize(destSize, destSize));
+ image.DebugSave(provider, appendPixelTypeToFileName: false);
+ }
+ }
+
+ [Theory]
+ [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)]
+ [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)]
+ [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)]
+ public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD)
+ where TPixel : struct, IPixel
+ {
+ // Basic test case, very helpful for debugging
+ // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means:
+ // resizing: (15, 12) -> (10, 6)
+ // kernel dimensions: (3, 4)
+
- provider.RunValidatingProcessorTest(
- ctx =>
- {
-
- SizeF newSize;
- string destSizeInfo;
- if (ratio.HasValue)
- {
- newSize = ctx.GetCurrentSize() * ratio.Value;
- destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
- }
- else
- {
- if (!specificDestWidth.HasValue || !specificDestHeight.HasValue)
- {
- throw new InvalidOperationException(
- "invalid dimensional input for Resize_WorksWithAllResamplers!");
- }
+ using (Image image = provider.GetImage())
+ {
+ var destSize = new Size(image.Width * wN / wD, image.Height * hN / hD);
+ image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false));
+ FormattableString outputInfo = $"({wN}÷{wD},{hN}÷{hD})";
+ image.DebugSave(provider, outputInfo, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(provider, outputInfo, appendPixelTypeToFileName: false);
+ }
+ }
- newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value);
- destSizeInfo = $"{newSize.Width}x{newSize.Height}";
- }
+ private static readonly int SizeOfVector4 = Unsafe.SizeOf();
- FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}";
- ctx.Apply(
- img => img.DebugSave(
- provider,
- $"{testOutputDetails}-ORIGINAL",
- appendPixelTypeToFileName: false));
- ctx.Resize((Size)newSize, sampler, false);
- return testOutputDetails;
- },
- comparer,
- appendPixelTypeToFileName: false);
+ [Theory]
+ [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)]
+ [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)]
+ [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)]
+ [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)]
+ [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)]
+ [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)]
+ [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)]
+ public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly(
+ TestImageProvider provider,
+ int workingBufferLimitInRows)
+ where TPixel : struct, IPixel
+ {
+ using (Image image0 = provider.GetImage())
+ {
+ Size destSize = image0.Size() / 4;
+
+ Configuration configuration = Configuration.CreateDefaultInstance();
+
+ int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4;
+ TestMemoryAllocator allocator = new TestMemoryAllocator();
+ configuration.MemoryAllocator = allocator;
+ configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes;
+
+ var verticalKernelMap = ResizeKernelMap.Calculate(
+ KnownResamplers.Bicubic,
+ destSize.Height,
+ image0.Height,
+ Configuration.Default.MemoryAllocator);
+ int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4;
+ verticalKernelMap.Dispose();
+
+ using (Image image = image0.Clone(configuration))
+ {
+ image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false));
+
+ image.DebugSave(
+ provider,
+ testOutputDetails: workingBufferLimitInRows,
+ appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ ImageComparer.TolerantPercentage(0.001f),
+ provider,
+ testOutputDetails: workingBufferLimitInRows,
+ appendPixelTypeToFileName: false);
+
+ Assert.NotEmpty(allocator.AllocationLog);
+
+ int maxAllocationSize = allocator.AllocationLog.Where(
+ e => e.ElementType == typeof(Vector4)).Max(e => e.LengthInBytes);
+
+ Assert.True(maxAllocationSize <= Math.Max(workingBufferSizeHintInBytes, minimumWorkerAllocationInBytes));
+ }
+ }
}
[Theory]
- [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 1)]
- [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 4)]
- [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 8)]
- [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, -1)]
- public void Resize_WorksWithAllParallelismLevels(TestImageProvider provider, int maxDegreeOfParallelism)
+ [WithTestPatternImages(100, 100, DefaultPixelType)]
+ public void Resize_Compand(TestImageProvider provider)
where TPixel : struct, IPixel
{
- provider.Configuration.MaxDegreeOfParallelism =
- maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount;
+ using (Image image = provider.GetImage())
+ {
+ image.Mutate(x => x.Resize(image.Size() / 2, true));
- FormattableString details = $"MDP{maxDegreeOfParallelism}";
+ image.DebugSave(provider);
+ image.CompareToReferenceOutput(ValidatorComparer, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)]
+ [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)]
+ public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand)
+ where TPixel : struct, IPixel
+ {
+ string details = compand ? "Compand" : "";
provider.RunValidatingProcessorTest(
- x => x.Resize(x.GetCurrentSize() / 2),
+ x => x.Resize(x.GetCurrentSize() / 2, compand),
details,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
[Theory]
- [WithTestPatternImages(100, 100, DefaultPixelType)]
- public void Resize_Compand(TestImageProvider provider)
+ [WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
+ public void Resize_IsAppliedToAllFrames(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
- image.Mutate(x => x.Resize(image.Size() / 2, true));
+ image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic));
- image.DebugSave(provider);
- image.CompareToReferenceOutput(ValidatorComparer, provider);
+ // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :(
+ image.DebugSave(provider, extension: "gif");
}
}
@@ -152,41 +209,112 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height))
{
Assert.ThrowsAny(
- () =>
- {
- image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true));
- });
+ () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); });
}
}
}
[Theory]
- [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)]
- [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)]
- public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand)
+ [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 1)]
+ [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 4)]
+ [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 8)]
+ [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, -1)]
+ public void Resize_WorksWithAllParallelismLevels(
+ TestImageProvider provider,
+ int maxDegreeOfParallelism)
where TPixel : struct, IPixel
{
- string details = compand ? "Compand" : "";
+ provider.Configuration.MaxDegreeOfParallelism =
+ maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount;
+
+ FormattableString details = $"MDP{maxDegreeOfParallelism}";
provider.RunValidatingProcessorTest(
- x => x.Resize(x.GetCurrentSize() / 2, compand),
+ x => x.Resize(x.GetCurrentSize() / 2),
details,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
-
+
[Theory]
- [WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
- public void Resize_IsAppliedToAllFrames(TestImageProvider provider)
+ [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), DefaultPixelType, 0.5f, null, null)]
+ [WithFileCollection(
+ nameof(CommonTestImages),
+ nameof(SmokeTestResamplerNames),
+ DefaultPixelType,
+ 0.3f,
+ null,
+ null)]
+ [WithFileCollection(
+ nameof(CommonTestImages),
+ nameof(SmokeTestResamplerNames),
+ DefaultPixelType,
+ 1.8f,
+ null,
+ null)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 0.5f, null, null)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, DefaultPixelType, 1f, null, null)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, DefaultPixelType, 8f, null, null)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, DefaultPixelType, null, 100, 99)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, DefaultPixelType, null, 300, 480)]
+ [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, DefaultPixelType, null, 301, 100)]
+ public void Resize_WorksWithAllResamplers(
+ TestImageProvider provider,
+ string samplerName,
+ float? ratio,
+ int? specificDestWidth,
+ int? specificDestHeight)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage())
- {
- image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic));
+ IResampler sampler = TestUtils.GetResampler(samplerName);
- // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :(
- image.DebugSave(provider, extension: Extensions.Gif);
- }
+ // NeirestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit
+ // most likely because of differences in numeric behavior.
+ // The difference is well visible when comparing output for
+ // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png
+ // TODO: Should we investigate this?
+ bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess
+ && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion)
+ && sampler is NearestNeighborResampler;
+
+ var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f);
+
+ // Let's make the working buffer size non-default:
+ provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4;
+
+ provider.RunValidatingProcessorTest(
+ ctx =>
+ {
+ SizeF newSize;
+ string destSizeInfo;
+ if (ratio.HasValue)
+ {
+ newSize = ctx.GetCurrentSize() * ratio.Value;
+ destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ if (!specificDestWidth.HasValue || !specificDestHeight.HasValue)
+ {
+ throw new InvalidOperationException(
+ "invalid dimensional input for Resize_WorksWithAllResamplers!");
+ }
+
+ newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value);
+ destSizeInfo = $"{newSize.Width}x{newSize.Height}";
+ }
+
+ FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}";
+ ctx.Apply(
+ img => img.DebugSave(
+ provider,
+ $"{testOutputDetails}-ORIGINAL",
+ appendPixelTypeToFileName: false));
+ ctx.Resize((Size)newSize, sampler, false);
+ return testOutputDetails;
+ },
+ comparer,
+ appendPixelTypeToFileName: false);
}
[Theory]
@@ -196,10 +324,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using (Image image = provider.GetImage())
{
- var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4);
+ var sourceRectangle = new Rectangle(
+ image.Width / 8,
+ image.Height / 8,
+ image.Width / 4,
+ image.Height / 4);
var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2);
- image.Mutate(x => x.Resize(image.Width, image.Height, KnownResamplers.Bicubic, sourceRectangle, destRectangle, false));
+ image.Mutate(
+ x => x.Resize(
+ image.Width,
+ image.Height,
+ KnownResamplers.Bicubic,
+ sourceRectangle,
+ destRectangle,
+ false));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
@@ -208,26 +347,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWidthAndKeepAspect(TestImageProvider provider)
+ public void ResizeHeightAndKeepAspect(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
- image.Mutate(x => x.Resize(image.Width / 3, 0, false));
+ image.Mutate(x => x.Resize(0, image.Height / 3, false));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
}
}
+ [Theory]
+ [WithTestPatternImages(10, 100, DefaultPixelType)]
+ public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ image.Mutate(x => x.Resize(0, 5));
+ Assert.Equal(1, image.Width);
+ Assert.Equal(5, image.Height);
+ }
+ }
+
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeHeightAndKeepAspect(TestImageProvider provider)
+ public void ResizeWidthAndKeepAspect(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
- image.Mutate(x => x.Resize(0, image.Height / 3, false));
+ image.Mutate(x => x.Resize(image.Width / 3, 0, false));
image.DebugSave(provider);
image.CompareToReferenceOutput(ValidatorComparer, provider);
@@ -247,30 +399,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
}
- [Theory]
- [WithTestPatternImages(10, 100, DefaultPixelType)]
- public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider)
- where TPixel : struct, IPixel
- {
- using (Image image = provider.GetImage())
- {
- image.Mutate(x => x.Resize(0, 5));
- Assert.Equal(1, image.Width);
- Assert.Equal(5, image.Height);
- }
- }
-
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWithCropWidthMode(TestImageProvider provider)
+ public void ResizeWithBoxPadMode(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
var options = new ResizeOptions
- {
- Size = new Size(image.Width / 2, image.Height)
- };
+ {
+ Size = new Size(image.Width + 200, image.Height + 200), Mode = ResizeMode.BoxPad
+ };
image.Mutate(x => x.Resize(options));
@@ -286,10 +425,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using (Image image = provider.GetImage())
{
- var options = new ResizeOptions
- {
- Size = new Size(image.Width, image.Height / 2)
- };
+ var options = new ResizeOptions { Size = new Size(image.Width, image.Height / 2) };
image.Mutate(x => x.Resize(options));
@@ -300,16 +436,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWithPadMode(TestImageProvider provider)
+ public void ResizeWithCropWidthMode(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
- var options = new ResizeOptions
- {
- Size = new Size(image.Width + 200, image.Height),
- Mode = ResizeMode.Pad
- };
+ var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) };
image.Mutate(x => x.Resize(options));
@@ -320,16 +452,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWithBoxPadMode(TestImageProvider provider)
+ public void ResizeWithMaxMode(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
- var options = new ResizeOptions
- {
- Size = new Size(image.Width + 200, image.Height + 200),
- Mode = ResizeMode.BoxPad
- };
+ var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max };
image.Mutate(x => x.Resize(options));
@@ -340,16 +468,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWithMaxMode(TestImageProvider provider)
+ public void ResizeWithMinMode(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
var options = new ResizeOptions
- {
- Size = new Size(300, 300),
- Mode = ResizeMode.Max
- };
+ {
+ Size = new Size(
+ (int)Math.Round(image.Width * .75F),
+ (int)Math.Round(image.Height * .95F)),
+ Mode = ResizeMode.Min
+ };
image.Mutate(x => x.Resize(options));
@@ -360,16 +490,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
- public void ResizeWithMinMode(TestImageProvider provider)
+ public void ResizeWithPadMode(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage())
{
var options = new ResizeOptions
- {
- Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)),
- Mode = ResizeMode.Min
- };
+ {
+ Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad
+ };
image.Mutate(x => x.Resize(options));
@@ -386,10 +515,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
using (Image image = provider.GetImage())
{
var options = new ResizeOptions
- {
- Size = new Size(image.Width / 2, image.Height),
- Mode = ResizeMode.Stretch
- };
+ {
+ Size = new Size(image.Width / 2, image.Height), Mode = ResizeMode.Stretch
+ };
image.Mutate(x => x.Resize(options));
@@ -397,61 +525,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
image.CompareToReferenceOutput(ValidatorComparer, provider);
}
}
-
- [Theory]
- [InlineData(-2, 0)]
- [InlineData(-1, 0)]
- [InlineData(0, 1)]
- [InlineData(1, 0)]
- [InlineData(2, 0)]
- public static void BicubicWindowOscillatesCorrectly(float x, float expected)
- {
- IResampler sampler = KnownResamplers.Bicubic;
- float result = sampler.GetValue(x);
-
- Assert.Equal(result, expected);
- }
-
- [Theory]
- [InlineData(-2, 0)]
- [InlineData(-1, 0)]
- [InlineData(0, 1)]
- [InlineData(1, 0)]
- [InlineData(2, 0)]
- public static void TriangleWindowOscillatesCorrectly(float x, float expected)
- {
- IResampler sampler = KnownResamplers.Triangle;
- float result = sampler.GetValue(x);
-
- Assert.Equal(result, expected);
- }
-
- [Theory]
- [InlineData(-2, 0)]
- [InlineData(-1, 0)]
- [InlineData(0, 1)]
- [InlineData(1, 0)]
- [InlineData(2, 0)]
- public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
- {
- IResampler sampler = KnownResamplers.Lanczos3;
- float result = sampler.GetValue(x);
-
- Assert.Equal(result, expected);
- }
-
- [Theory]
- [InlineData(-4, 0)]
- [InlineData(-2, 0)]
- [InlineData(0, 1)]
- [InlineData(2, 0)]
- [InlineData(4, 0)]
- public static void Lanczos5WindowOscillatesCorrectly(float x, float expected)
- {
- IResampler sampler = KnownResamplers.Lanczos5;
- float result = sampler.GetValue(x);
-
- Assert.Equal(result, expected);
- }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs
new file mode 100644
index 000000000..1e4324e04
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Reflection;
+
+namespace SixLabors.ImageSharp.Tests
+{
+ public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase
+ {
+ public WithBasicTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters)
+ : this(null, width, height, pixelTypes, additionalParameters)
+ {
+ }
+
+ public WithBasicTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters)
+ : base(memberData, pixelTypes, additionalParameters)
+ {
+ this.Width = width;
+ this.Height = height;
+ }
+
+ ///
+ /// Gets the width
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height
+ ///
+ public int Height { get; }
+
+ protected override string GetFactoryMethodName(MethodInfo testMethod) => "BasicTestPattern";
+
+ protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height };
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs
new file mode 100644
index 000000000..de203535c
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests
+{
+ public abstract partial class TestImageProvider
+ {
+ private class BasicTestPatternProvider : BlankProvider
+ {
+ public BasicTestPatternProvider(int width, int height)
+ : base(width, height)
+ {
+ }
+
+ ///
+ /// This parameterless constructor is needed for xUnit deserialization
+ ///
+ public BasicTestPatternProvider()
+ {
+ }
+
+ public override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}");
+
+ public override Image GetImage()
+ {
+ var result = new Image(this.Configuration, this.Width, this.Height);
+
+ TPixel topLeftColor = NamedColors.Red;
+ TPixel topRightColor = NamedColors.Green;
+ TPixel bottomLeftColor = NamedColors.Blue;
+
+ // Transparent purple:
+ TPixel bottomRightColor = default;
+ bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f));
+
+ int midY = this.Height / 2;
+ int midX = this.Width / 2;
+
+ for (int y = 0; y < midY; y++)
+ {
+ Span row = result.GetPixelRowSpan(y);
+
+ row.Slice(0, midX).Fill(topLeftColor);
+ row.Slice(midX, this.Width-midX).Fill(topRightColor);
+ }
+
+ for (int y = midY; y < this.Height; y++)
+ {
+ Span row = result.GetPixelRowSpan(y);
+
+ row.Slice(0, midX).Fill(bottomLeftColor);
+ row.Slice(midX, this.Width-midX).Fill(bottomRightColor);
+ }
+
+ return result;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs
index 7821d0b51..dae2f0cfe 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs
@@ -20,6 +20,9 @@ namespace SixLabors.ImageSharp.Tests
this.Height = height;
}
+ ///
+ /// This parameterless constructor is needed for xUnit deserialization
+ ///
public BlankProvider()
{
this.Width = 100;
@@ -32,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests
protected int Width { get; private set; }
- public override Image GetImage() => new Image(this.Width, this.Height);
+ public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height);
public override void Deserialize(IXunitSerializationInfo info)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
index 3ed696c47..8c5b88b28 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
@@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests
Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder));
- return cachedImage.Clone();
+ return cachedImage.Clone(this.Configuration);
}
public override void Deserialize(IXunitSerializationInfo info)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs
index d68c37a76..1ff95f60d 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs
@@ -35,6 +35,9 @@ namespace SixLabors.ImageSharp.Tests
this.a = a;
}
+ ///
+ /// This parameterless constructor is needed for xUnit deserialization
+ ///
public SolidProvider()
: base()
{
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
index 5b5e4740a..15fab9b2b 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
@@ -44,6 +44,12 @@ namespace SixLabors.ImageSharp.Tests
public string MethodName { get; private set; }
public string OutputSubfolderName { get; private set; }
+ public static TestImageProvider BasicTestPattern(int width,
+ int height,
+ MethodInfo testMethod = null,
+ PixelTypes pixelTypeOverride = PixelTypes.Undefined)
+ => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride);
+
public static TestImageProvider TestPattern(
int width,
int height,
@@ -100,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests
///
public Image GetImage(Action> operationsToApply)
{
- Image img = GetImage();
+ Image img = this.GetImage();
img.Mutate(operationsToApply);
return img;
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
index 17e5369d4..6df8c8501 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Net.Mime;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
@@ -25,8 +26,10 @@ namespace SixLabors.ImageSharp.Tests
{
}
+ ///
+ /// This parameterless constructor is needed for xUnit deserialization
+ ///
public TestPatternProvider()
- : base()
{
}
@@ -42,9 +45,8 @@ namespace SixLabors.ImageSharp.Tests
DrawTestPattern(image);
TestImages.Add(this.SourceFileOrDescription, image);
}
+ return TestImages[this.SourceFileOrDescription].Clone(this.Configuration);
}
-
- return TestImages[this.SourceFileOrDescription].Clone();
}
///
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
index e3d8bf380..4ccb38745 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
@@ -23,14 +23,19 @@ namespace SixLabors.ImageSharp.Tests
{
float[] values = new float[length];
- for (int i = 0; i < length; i++)
- {
- values[i] = GetRandomFloat(rnd, minVal, maxVal);
- }
+ RandomFill(rnd, values, minVal, maxVal);
return values;
}
+ public static void RandomFill(this Random rnd, Span destination, float minVal, float maxVal)
+ {
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destination[i] = GetRandomFloat(rnd, minVal, maxVal);
+ }
+ }
+
///
/// Creates an of the given length consisting of random values between the two ranges.
///
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs
index dc755e682..5613e7b68 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs
@@ -1,5 +1,7 @@
using System;
using System.Buffers;
+using System.Collections.Generic;
+using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.Memory;
@@ -8,6 +10,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
internal class TestMemoryAllocator : MemoryAllocator
{
+ private List allocationLog = new List();
+
public TestMemoryAllocator(byte dirtyValue = 42)
{
this.DirtyValue = dirtyValue;
@@ -18,10 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Memory
///
public byte DirtyValue { get; }
+ public IList AllocationLog => this.allocationLog;
+
public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
- {
+ {
T[] array = this.AllocateArray(length, options);
-
return new BasicArrayBuffer(array, length);
}
@@ -34,6 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
private T[] AllocateArray(int length, AllocationOptions options)
where T : struct
{
+ this.allocationLog.Add(AllocationRequest.Create(options, length));
var array = new T[length + 42];
if (options == AllocationOptions.None)
@@ -44,6 +50,35 @@ namespace SixLabors.ImageSharp.Tests.Memory
return array;
}
+
+ public struct AllocationRequest
+ {
+ private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes)
+ {
+ this.ElementType = elementType;
+ this.AllocationOptions = allocationOptions;
+ this.Length = length;
+ this.LengthInBytes = lengthInBytes;
+
+ if (elementType == typeof(Vector4))
+ {
+
+ }
+ }
+
+ public static AllocationRequest Create(AllocationOptions allocationOptions, int length)
+ {
+ Type type = typeof(T);
+ int elementSize = Marshal.SizeOf(type);
+ return new AllocationRequest(type, allocationOptions, length, length * elementSize);
+ }
+
+ public Type ElementType { get; }
+ public AllocationOptions AllocationOptions { get; }
+ public int Length { get; }
+ public int LengthInBytes { get; }
+ }
+
///
/// Wraps an array as an instance.
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
index cac7828e9..4ef6a582c 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
@@ -4,112 +4,135 @@
using System;
using System.Collections.Concurrent;
using System.IO;
+
+using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
+
using Xunit;
using Xunit.Abstractions;
+
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests
{
public class TestImageProviderTests
{
+ public static readonly TheoryData