diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
index b88c229c5..f9bbdfc04 100644
--- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
+++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs
@@ -4,6 +4,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
@@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp
///
/// The to premultiply
/// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Premultiply(this Vector4 source)
{
float w = source.W;
@@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp
///
/// The to premultiply
/// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 UnPremultiply(this Vector4 source)
{
float w = source.W;
@@ -42,6 +43,42 @@ namespace SixLabors.ImageSharp
return unpremultiplied;
}
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ public static void Premultiply(Span vectors)
+ {
+ // TODO: This method can be AVX2 optimized using Vector
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ var s = new Vector4(v.W);
+ s.W = 1;
+ v *= s;
+ }
+ }
+
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ public static void UnPremultiply(Span vectors)
+ {
+ // TODO: This method can be AVX2 optimized using Vector
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ var s = new Vector4(1 / v.W);
+ s.W = 1;
+ v *= s;
+ }
+ }
+
///
/// Compresses a linear color signal to its sRGB equivalent.
///
@@ -49,7 +86,7 @@ namespace SixLabors.ImageSharp
///
/// The whose signal to compress.
/// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Compress(this Vector4 linear)
{
// TODO: Is there a faster way to do this?
@@ -63,13 +100,47 @@ namespace SixLabors.ImageSharp
///
/// The whose signal to expand.
/// The .
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Expand(this Vector4 gamma)
{
// TODO: Is there a faster way to do this?
return new Vector4(Expand(gamma.X), Expand(gamma.Y), Expand(gamma.Z), gamma.W);
}
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ public static void Compress(Span vectors)
+ {
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ v.X = Compress(v.X);
+ v.Y = Compress(v.Y);
+ v.Z = Compress(v.Z);
+ }
+ }
+
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ public static void Expand(Span vectors)
+ {
+ ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
+
+ for (int i = 0; i < vectors.Length; i++)
+ {
+ ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
+ v.X = Expand(v.X);
+ v.Y = Expand(v.Y);
+ v.Z = Expand(v.Z);
+ }
+ }
+
///
/// Gets the compressed sRGB value from an linear signal.
///
@@ -79,7 +150,7 @@ namespace SixLabors.ImageSharp
///
/// The .
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
private static float Compress(float signal)
{
if (signal <= 0.0031308F)
@@ -99,7 +170,7 @@ namespace SixLabors.ImageSharp
///
/// The .
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
private static float Expand(float signal)
{
if (signal <= 0.04045F)
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index c15e0a732..d672cfd5a 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
+
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -13,6 +14,31 @@ namespace SixLabors.ImageSharp
///
internal static class ImageMaths
{
+ ///
+ /// Determine the Greatest CommonDivisor (GCD) of two numbers.
+ ///
+ public static int GreatestCommonDivisor(int a, int b)
+ {
+ while (b != 0)
+ {
+ int temp = b;
+ b = a % b;
+ a = temp;
+ }
+
+ return a;
+ }
+
+ ///
+ /// Determine the Least Common Multiple (LCM) of two numbers.
+ /// TODO: This method might be useful for building a more compact
+ ///
+ public static int LeastCommonMultiple(int a, int b)
+ {
+ // https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor
+ return (a / GreatestCommonDivisor(a, b)) * b;
+ }
+
///
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
new file mode 100644
index 000000000..277be53ff
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Holds the values in an optimized contigous memory region.
+ ///
+ internal class KernelMap : IDisposable
+ {
+ private readonly Buffer2D data;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use for allocations.
+ /// The size of the destination window
+ /// The radius of the kernel
+ public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius)
+ {
+ int width = (int)Math.Ceiling(kernelRadius * 2);
+ this.data = memoryAllocator.Allocate2D(width, destinationSize, AllocationOptions.Clean);
+ this.Kernels = new ResizeKernel[destinationSize];
+ }
+
+ ///
+ /// Gets the calculated values.
+ ///
+ public ResizeKernel[] Kernels { get; }
+
+ ///
+ /// Disposes instance releasing it's backing buffer.
+ ///
+ public void Dispose()
+ {
+ this.data.Dispose();
+ }
+
+ ///
+ /// Computes the weights to apply at each pixel when resizing.
+ ///
+ /// The
+ /// The destination size
+ /// The source size
+ /// The to use for buffer allocations
+ /// The
+ public static KernelMap Calculate(
+ IResampler sampler,
+ int destinationSize,
+ int sourceSize,
+ MemoryAllocator memoryAllocator)
+ {
+ float ratio = (float)sourceSize / destinationSize;
+ float scale = ratio;
+
+ if (scale < 1F)
+ {
+ scale = 1F;
+ }
+
+ float radius = MathF.Ceiling(scale * sampler.Radius);
+ var result = new KernelMap(memoryAllocator, destinationSize, radius);
+
+ for (int i = 0; i < destinationSize; i++)
+ {
+ float center = ((i + .5F) * ratio) - .5F;
+
+ // Keep inside bounds.
+ int left = (int)MathF.Ceiling(center - radius);
+ if (left < 0)
+ {
+ left = 0;
+ }
+
+ int right = (int)MathF.Floor(center + radius);
+ if (right > sourceSize - 1)
+ {
+ right = sourceSize - 1;
+ }
+
+ float sum = 0;
+
+ ResizeKernel ws = result.CreateKernel(i, left, right);
+ result.Kernels[i] = ws;
+
+ ref float weightsBaseRef = ref ws.GetStartReference();
+
+ for (int j = left; j <= right; j++)
+ {
+ float weight = sampler.GetValue((j - center) / scale);
+ sum += weight;
+
+ // weights[j - left] = weight:
+ Unsafe.Add(ref weightsBaseRef, j - left) = weight;
+ }
+
+ // Normalize, best to do it here rather than in the pixel loop later on.
+ if (sum > 0)
+ {
+ for (int w = 0; w < ws.Length; w++)
+ {
+ // weights[w] = weights[w] / sum:
+ ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
+ wRef /= sum;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Slices a weights value at the given positions.
+ ///
+ /// The index in destination buffer
+ /// The local left index value
+ /// The local right index value
+ /// The weights
+ private ResizeKernel CreateKernel(int destIdx, int leftIdx, int rightIdx)
+ {
+ return new ResizeKernel(destIdx, leftIdx, this.data, rightIdx - leftIdx + 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
new file mode 100644
index 000000000..cc3c20453
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
@@ -0,0 +1,95 @@
+// 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.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Transforms
+{
+ ///
+ /// Points to a collection of of weights allocated in .
+ ///
+ internal struct ResizeKernel
+ {
+ ///
+ /// The local left index position
+ ///
+ public int Left;
+
+ ///
+ /// The length of the weights window
+ ///
+ public int Length;
+
+ ///
+ /// The buffer containing the weights values.
+ ///
+ private readonly Memory buffer;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The destination index in the buffer
+ /// The local left index
+ /// The span
+ /// The length of the window
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal ResizeKernel(int index, int left, Buffer2D buffer, int length)
+ {
+ int flatStartIndex = index * buffer.Width;
+ this.Left = left;
+ this.buffer = buffer.MemorySource.Memory.Slice(flatStartIndex, length);
+ this.Length = length;
+ }
+
+ ///
+ /// Gets a reference to the first item of the window.
+ ///
+ /// The reference to the first item of the window
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public ref float GetStartReference()
+ {
+ Span span = this.buffer.Span;
+ return ref span[0];
+ }
+
+ ///
+ /// Gets the span representing the portion of the that this window covers
+ ///
+ /// The
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Span GetSpan() => this.buffer.Span;
+
+ ///
+ /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
+ ///
+ /// The input span of vectors
+ /// The source row position.
+ /// The weighted sum
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Vector4 Convolve(Span rowSpan, int sourceX)
+ {
+ ref float horizontalValues = ref this.GetStartReference();
+ int left = this.Left;
+ ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX);
+
+ // Destination color components
+ Vector4 result = Vector4.Zero;
+
+ for (int i = 0; i < this.Length; i++)
+ {
+ float weight = Unsafe.Add(ref horizontalValues, i);
+ Vector4 v = Unsafe.Add(ref vecPtr, i);
+ result += v * weight;
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
index 53cd9e9d3..d353c1fd2 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
@@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Threading.Tasks;
+
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
@@ -27,8 +26,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
where TPixel : struct, IPixel
{
// The following fields are not immutable but are optionally created on demand.
- private WeightsBuffer horizontalWeights;
- private WeightsBuffer verticalWeights;
+ private KernelMap horizontalKernelMap;
+ private KernelMap verticalKernelMap;
///
/// Initializes a new instance of the class.
@@ -148,76 +147,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
public bool Compand { get; }
- ///
- /// Computes the weights to apply at each pixel when resizing.
- ///
- /// The to use for buffer allocations
- /// The destination size
- /// The source size
- /// The
- // TODO: Made internal to simplify experimenting with weights data. Make it private when finished figuring out how to optimize all the stuff!
- internal WeightsBuffer PrecomputeWeights(MemoryAllocator memoryAllocator, int destinationSize, int sourceSize)
- {
- float ratio = (float)sourceSize / destinationSize;
- float scale = ratio;
-
- if (scale < 1F)
- {
- scale = 1F;
- }
-
- IResampler sampler = this.Sampler;
- float radius = MathF.Ceiling(scale * sampler.Radius);
- var result = new WeightsBuffer(memoryAllocator, sourceSize, destinationSize);
-
- for (int i = 0; i < destinationSize; i++)
- {
- float center = ((i + .5F) * ratio) - .5F;
-
- // Keep inside bounds.
- int left = (int)MathF.Ceiling(center - radius);
- if (left < 0)
- {
- left = 0;
- }
-
- int right = (int)MathF.Floor(center + radius);
- if (right > sourceSize - 1)
- {
- right = sourceSize - 1;
- }
-
- float sum = 0;
-
- WeightsWindow ws = result.GetWeightsWindow(i, left, right);
- result.Weights[i] = ws;
-
- ref float weightsBaseRef = ref ws.GetStartReference();
-
- for (int j = left; j <= right; j++)
- {
- float weight = sampler.GetValue((j - center) / scale);
- sum += weight;
-
- // weights[j - left] = weight:
- Unsafe.Add(ref weightsBaseRef, j - left) = weight;
- }
-
- // Normalize, best to do it here rather than in the pixel loop later on.
- if (sum > 0)
- {
- for (int w = 0; w < ws.Length; w++)
- {
- // weights[w] = weights[w] / sum:
- ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
- wRef /= sum;
- }
- }
- }
-
- return result;
- }
-
///
protected override Image CreateDestination(Image source, Rectangle sourceRectangle)
{
@@ -235,15 +164,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
// Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
- this.horizontalWeights = this.PrecomputeWeights(
- memoryAllocator,
+ this.horizontalKernelMap = KernelMap.Calculate(
+ this.Sampler,
this.ResizeRectangle.Width,
- sourceRectangle.Width);
+ sourceRectangle.Width,
+ memoryAllocator);
- this.verticalWeights = this.PrecomputeWeights(
- memoryAllocator,
+ this.verticalKernelMap = KernelMap.Calculate(
+ this.Sampler,
this.ResizeRectangle.Height,
- sourceRectangle.Height);
+ sourceRectangle.Height,
+ memoryAllocator);
}
}
@@ -303,14 +234,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
+ int sourceHeight = source.Height;
+
// 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.
- // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
- using (Buffer2D firstPassPixels = source.MemoryAllocator.Allocate2D(width, source.Height))
+ using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(sourceHeight, width))
{
- firstPassPixels.MemorySource.Clear();
+ firstPassPixelsTransposed.MemorySource.Clear();
var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom);
@@ -321,30 +253,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
for (int y = rows.Min; y < rows.Max; y++)
{
- ref Vector4 firstPassRow =
- ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y));
Span sourceRow = source.GetPixelRowSpan(y);
Span tempRowSpan = tempRowBuffer.Span;
PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length);
+ Vector4Extensions.Premultiply(tempRowSpan);
+
+ ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y];
if (this.Compand)
{
- for (int x = minX; x < maxX; x++)
- {
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
- Unsafe.Add(ref firstPassRow, x) =
- window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX);
- }
+ Vector4Extensions.Expand(tempRowSpan);
}
- else
+
+ for (int x = minX; x < maxX; x++)
{
- for (int x = minX; x < maxX; x++)
- {
- WeightsWindow window = this.horizontalWeights.Weights[x - startX];
- Unsafe.Add(ref firstPassRow, x) =
- window.ComputeWeightedRowSum(tempRowSpan, sourceX);
- }
+ ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX];
+ Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) =
+ window.Convolve(tempRowSpan, sourceX);
}
}
});
@@ -352,46 +278,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY);
// Now process the rows.
- ParallelHelper.IterateRows(
+ ParallelHelper.IterateRowsWithTempBuffer(
processRowsRect,
configuration,
- rows =>
+ (rows, tempRowBuffer) =>
{
+ Span tempRowSpan = tempRowBuffer.Span;
+
for (int y = rows.Min; y < rows.Max; y++)
{
// Ensure offsets are normalized for cropping and padding.
- WeightsWindow window = this.verticalWeights.Weights[y - startY];
- ref TPixel targetRow = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y));
+ ResizeKernel window = this.verticalKernelMap.Kernels[y - startY];
- if (this.Compand)
+ ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan);
+
+ for (int x = 0; x < width; x++)
{
- for (int x = 0; x < width; x++)
- {
- // Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(
- firstPassPixels,
- x,
- sourceY);
- destinationVector = destinationVector.Compress();
-
- ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
- pixel.PackFromVector4(destinationVector);
- }
+ Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x);
+
+ // Destination color components
+ Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY);
}
- else
+
+ Vector4Extensions.UnPremultiply(tempRowSpan);
+
+ if (this.Compand)
{
- for (int x = 0; x < width; x++)
- {
- // Destination color components
- Vector4 destinationVector = window.ComputeWeightedColumnSum(
- firstPassPixels,
- x,
- sourceY);
-
- ref TPixel pixel = ref Unsafe.Add(ref targetRow, x);
- pixel.PackFromVector4(destinationVector);
- }
+ Vector4Extensions.Compress(tempRowSpan);
}
+
+ Span targetRowSpan = destination.GetPixelRowSpan(y);
+ PixelOperations.Instance.PackFromVector4(tempRowSpan, targetRowSpan, tempRowSpan.Length);
}
});
}
@@ -402,10 +319,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
base.AfterImageApply(source, destination, sourceRectangle);
// TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable!
- this.horizontalWeights?.Dispose();
- this.horizontalWeights = null;
- this.verticalWeights?.Dispose();
- this.verticalWeights = null;
+ this.horizontalKernelMap?.Dispose();
+ this.horizontalKernelMap = null;
+ this.verticalKernelMap?.Dispose();
+ this.verticalKernelMap = null;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
deleted file mode 100644
index 68133a548..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// Holds the values in an optimized contigous memory region.
- ///
- internal class WeightsBuffer : IDisposable
- {
- private readonly Buffer2D dataBuffer;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The to use for allocations.
- /// The size of the source window
- /// The size of the destination window
- public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize)
- {
- this.dataBuffer = memoryAllocator.Allocate2D(sourceSize, destinationSize, AllocationOptions.Clean);
- this.Weights = new WeightsWindow[destinationSize];
- }
-
- ///
- /// Gets the calculated values.
- ///
- public WeightsWindow[] Weights { get; }
-
- ///
- /// Disposes instance releasing it's backing buffer.
- ///
- public void Dispose()
- {
- this.dataBuffer.Dispose();
- }
-
- ///
- /// Slices a weights value at the given positions.
- ///
- /// The index in destination buffer
- /// The local left index value
- /// The local right index value
- /// The weights
- public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
- {
- return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
deleted file mode 100644
index 01cf97e59..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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.Memory;
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// Points to a collection of of weights allocated in .
- ///
- internal struct WeightsWindow
- {
- ///
- /// The local left index position
- ///
- public int Left;
-
- ///
- /// The length of the weights window
- ///
- public int Length;
-
- ///
- /// The index in the destination buffer
- ///
- private readonly int flatStartIndex;
-
- ///
- /// The buffer containing the weights values.
- ///
- private readonly MemorySource buffer;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The destination index in the buffer
- /// The local left index
- /// The span
- /// The length of the window
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal WeightsWindow(int index, int left, Buffer2D buffer, int length)
- {
- this.flatStartIndex = (index * buffer.Width) + left;
- this.Left = left;
- this.buffer = buffer.MemorySource;
- this.Length = length;
- }
-
- ///
- /// Gets a reference to the first item of the window.
- ///
- /// The reference to the first item of the window
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ref float GetStartReference()
- {
- Span span = this.buffer.GetSpan();
- return ref span[this.flatStartIndex];
- }
-
- ///
- /// Gets the span representing the portion of the that this window covers
- ///
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span GetWindowSpan() => this.buffer.GetSpan().Slice(this.flatStartIndex, this.Length);
-
- ///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
- ///
- /// The input span of vectors
- /// The source row position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX)
- {
- ref float horizontalValues = ref this.GetStartReference();
- int left = this.Left;
- ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX);
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float weight = Unsafe.Add(ref horizontalValues, i);
- Vector4 v = Unsafe.Add(ref vecPtr, i);
- result += v.Premultiply() * weight;
- }
-
- return result;
- }
-
- ///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
- /// Applies to all input vectors.
- ///
- /// The input span of vectors
- /// The source row position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX)
- {
- ref float horizontalValues = ref this.GetStartReference();
- int left = this.Left;
- ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX);
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float weight = Unsafe.Add(ref horizontalValues, i);
- Vector4 v = Unsafe.Add(ref vecPtr, i);
- result += v.Premultiply().Expand() * weight;
- }
-
- return result.UnPremultiply();
- }
-
- ///
- /// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x',
- /// weighted by weight values, pointed by this instance.
- ///
- /// The buffer of input vectors in row first order
- /// The row position
- /// The source column position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY)
- {
- ref float verticalValues = ref this.GetStartReference();
- int left = this.Left;
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float yw = Unsafe.Add(ref verticalValues, i);
- int index = left + i + sourceY;
- result += firstPassPixels[x, index] * yw;
- }
-
- return result.UnPremultiply();
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
index 86dc13e91..f53061d4e 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs
@@ -1,7 +1,5 @@
-//
-// Copyright (c) James Jackson-South and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-//
using System;
using System.Drawing;
@@ -9,90 +7,91 @@ using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
-using CoreSize = SixLabors.Primitives.Size;
-
namespace SixLabors.ImageSharp.Benchmarks
{
- using System.Threading.Tasks;
-
- using SixLabors.ImageSharp.Formats.Jpeg;
-
[Config(typeof(Config.ShortClr))]
- public class Resize : BenchmarkBase
+ public abstract class ResizeBenchmarkBase
{
- private readonly Configuration configuration = new Configuration(new JpegConfigurationModule());
+ protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule());
+
+ private Image sourceImage;
+
+ private Bitmap sourceBitmap;
+
+ public const int SourceSize = 3032;
- [Params(false, true)]
- public bool EnableParallelExecution { get; set; }
+ public const int DestSize = 400;
[GlobalSetup]
public void Setup()
{
- this.configuration.MaxDegreeOfParallelism =
- this.EnableParallelExecution ? Environment.ProcessorCount : 1;
+ this.sourceImage = new Image(this.Configuration, SourceSize, SourceSize);
+ this.sourceBitmap = new Bitmap(SourceSize, SourceSize);
}
- [Benchmark(Baseline = true, Description = "System.Drawing Resize")]
- public Size ResizeSystemDrawing()
+ [GlobalCleanup]
+ public void Cleanup()
{
- using (Bitmap source = new Bitmap(2000, 2000))
+ this.sourceImage.Dispose();
+ this.sourceBitmap.Dispose();
+ }
+
+ [Benchmark(Baseline = true)]
+ public int SystemDrawing()
+ {
+ using (var destination = new Bitmap(DestSize, DestSize))
{
- using (Bitmap destination = new Bitmap(400, 400))
+ using (var graphics = Graphics.FromImage(destination))
{
- using (Graphics graphics = Graphics.FromImage(destination))
- {
- graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
- graphics.CompositingQuality = CompositingQuality.HighQuality;
- graphics.DrawImage(source, 0, 0, 400, 400);
- }
-
- return destination.Size;
+ graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
+ graphics.CompositingQuality = CompositingQuality.HighQuality;
+ graphics.DrawImage(this.sourceBitmap, 0, 0, DestSize, DestSize);
}
+
+ return destination.Width;
}
}
- [Benchmark(Description = "ImageSharp Resize")]
- public CoreSize ResizeCore()
+ [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")]
+ public int ImageSharp_P1() => this.RunImageSharpResize(1);
+
+ [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")]
+ public int ImageSharp_P4() => this.RunImageSharpResize(4);
+
+ [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")]
+ public int ImageSharp_P8() => this.RunImageSharpResize(8);
+
+ protected int RunImageSharpResize(int maxDegreeOfParallelism)
{
- using (var image = new Image(this.configuration, 2000, 2000))
+ this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+
+ using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation))
{
- image.Mutate(x => x.Resize(400, 400));
- return new CoreSize(image.Width, image.Height);
+ return clone.Width;
}
}
- //[Benchmark(Description = "ImageSharp Vector Resize")]
- //public CoreSize ResizeCoreVector()
- //{
- // using (Image image = new Image(2000, 2000))
- // {
- // image.Resize(400, 400);
- // return new CoreSize(image.Width, image.Height);
- // }
- //}
-
- //[Benchmark(Description = "ImageSharp Compand Resize")]
- //public CoreSize ResizeCoreCompand()
- //{
- // using (Image image = new Image(2000, 2000))
- // {
- // image.Resize(400, 400, true);
- // return new CoreSize(image.Width, image.Height);
- // }
- //}
-
- //[Benchmark(Description = "ImageSharp Vector Compand Resize")]
- //public CoreSize ResizeCoreVectorCompand()
- //{
- // using (Image image = new Image(2000, 2000))
- // {
- // image.Resize(400, 400, true);
- // return new CoreSize(image.Width, image.Height);
- // }
- //}
+ protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx);
+ }
+
+ public class Resize_Bicubic : ResizeBenchmarkBase
+ {
+ protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
+ {
+ ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic);
+ }
+ }
+
+ public class Resize_BicubicCompand : ResizeBenchmarkBase
+ {
+ protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
+ {
+ ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic, true);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
new file mode 100644
index 000000000..61f06da9f
--- /dev/null
+++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Helpers
+{
+ public class ImageMathsTests
+ {
+ [Theory]
+ [InlineData(1, 1, 1)]
+ [InlineData(1, 42, 1)]
+ [InlineData(10, 8, 2)]
+ [InlineData(12, 18, 6)]
+ [InlineData(4536, 1000, 8)]
+ [InlineData(1600, 1024, 64)]
+ public void GreatestCommonDivisor(int a, int b, int expected)
+ {
+ int actual = ImageMaths.GreatestCommonDivisor(a, b);
+
+ Assert.Equal(expected, actual);
+ }
+
+ [Theory]
+ [InlineData(1, 1, 1)]
+ [InlineData(1, 42, 42)]
+ [InlineData(3, 4, 12)]
+ [InlineData(6, 4, 12)]
+ [InlineData(1600, 1024, 25600)]
+ [InlineData(3264, 100, 81600)]
+ public void LeastCommonMultiple(int a, int b, int expected)
+ {
+ int actual = ImageMaths.LeastCommonMultiple(a, b);
+
+ Assert.Equal(expected, actual);
+ }
+
+
+ // TODO: We need to test all ImageMaths methods!
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs
new file mode 100644
index 000000000..68f71d88f
--- /dev/null
+++ b/tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Linq;
+using System.Numerics;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Helpers
+{
+ public class Vector4ExtensionsTests
+ {
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(30)]
+ public void Premultiply_VectorSpan(int length)
+ {
+ var rnd = new Random(42);
+ Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
+ Vector4[] expected = source.Select(v => v.Premultiply()).ToArray();
+
+ Vector4Extensions.Premultiply(source);
+
+ Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(30)]
+ public void UnPremultiply_VectorSpan(int length)
+ {
+ var rnd = new Random(42);
+ Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
+ Vector4[] expected = source.Select(v => v.UnPremultiply()).ToArray();
+
+ Vector4Extensions.UnPremultiply(source);
+
+ Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(30)]
+ public void Expand_VectorSpan(int length)
+ {
+ var rnd = new Random(42);
+ Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
+ Vector4[] expected = source.Select(v => v.Expand()).ToArray();
+
+ Vector4Extensions.Expand(source);
+
+ Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(30)]
+ public void Compress_VectorSpan(int length)
+ {
+ var rnd = new Random(42);
+ Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
+ Vector4[] expected = source.Select(v => v.Compress()).ToArray();
+
+ Vector4Extensions.Compress(source);
+
+ Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ }
+
+
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs
new file mode 100644
index 000000000..1b4b3cf6a
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+using System.Text;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.Primitives;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
+{
+ public class KernelMapTests
+ {
+ private ITestOutputHelper Output { get; }
+
+ public KernelMapTests(ITestOutputHelper output)
+ {
+ this.Output = output;
+ }
+
+ [Theory(Skip = "TODO: Add asserionts")]
+ [InlineData(500, 200, nameof(KnownResamplers.Bicubic))]
+ [InlineData(50, 40, nameof(KnownResamplers.Bicubic))]
+ [InlineData(40, 30, nameof(KnownResamplers.Bicubic))]
+ [InlineData(500, 200, nameof(KnownResamplers.Lanczos8))]
+ [InlineData(100, 80, nameof(KnownResamplers.Lanczos8))]
+ [InlineData(100, 10, nameof(KnownResamplers.Lanczos8))]
+ [InlineData(10, 100, nameof(KnownResamplers.Lanczos8))]
+ public void PrintKernelMap(int srcSize, int destSize, string resamplerName)
+ {
+ var resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null);
+
+ var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
+
+ var bld = new StringBuilder();
+
+ foreach (ResizeKernel window in kernelMap.Kernels)
+ {
+ Span span = window.GetSpan();
+ for (int i = 0; i < window.Length; i++)
+ {
+ float value = span[i];
+ bld.Append($"{value,7:F4}");
+ bld.Append("| ");
+ }
+
+ bld.AppendLine();
+ }
+
+ string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintKernelMap));
+ string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD";
+
+ File.WriteAllText(fileName, bld.ToString());
+
+ this.Output.WriteLine(bld.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
index d5f015404..e24458d38 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
@@ -1,69 +1,47 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.IO;
-using System.Text;
-
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
-using SixLabors.ImageSharp.Processing.Processors.Transforms;
-using SixLabors.Primitives;
+using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
public class ResizeProfilingBenchmarks : MeasureFixture
{
+ public const string SkipText =
+#if false
+ null;
+#else
+ "Benchmark, enable manually!";
+#endif
+
+ private readonly Configuration configuration = Configuration.CreateDefaultInstance();
+
public ResizeProfilingBenchmarks(ITestOutputHelper output)
: base(output)
{
+ this.configuration.MaxDegreeOfParallelism = 1;
}
public int ExecutionCount { get; set; } = 50;
-
- // [Theory] // Benchmark, enable manually!
- // [InlineData(100, 100)]
- // [InlineData(2000, 2000)]
+
+ [Theory(Skip = SkipText)]
+ [InlineData(100, 100)]
+ [InlineData(2000, 2000)]
public void ResizeBicubic(int width, int height)
{
this.Measure(this.ExecutionCount,
() =>
{
- using (var image = new Image(width, height))
+ using (var image = new Image(this.configuration, width, height))
{
- image.Mutate(x => x.Resize(width / 4, height / 4));
+ image.Mutate(x => x.Resize(width / 5, height / 5));
}
});
}
- // [Fact]
- public void PrintWeightsData()
- {
- var size = new Size(500, 500);
- var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200, size);
-
- WeightsBuffer weights = proc.PrecomputeWeights(Configuration.Default.MemoryAllocator, proc.Width, size.Width);
-
- var bld = new StringBuilder();
-
- foreach (WeightsWindow window in weights.Weights)
- {
- Span span = window.GetWindowSpan();
- for (int i = 0; i < window.Length; i++)
- {
- float value = span[i];
- bld.Append(value);
- bld.Append("| ");
- }
-
- bld.AppendLine();
- }
-
- File.WriteAllText("BicubicWeights.MD", bld.ToString());
-
- // this.Output.WriteLine(bld.ToString());
- }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index d1d473bbd..bec64e4d3 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -11,14 +11,15 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
+
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
public class ResizeTests : FileTestBase
{
public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
- private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F);
-
+ private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F);
+
public static readonly TheoryData AllReSamplers =
new TheoryData
{
@@ -52,10 +53,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
FormattableString details = $"{name}-{ratio.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
image.DebugSave(provider, details);
- image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.005f), provider, details);
+ image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.02f), provider, details);
}
}
+ [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)
+ where TPixel : struct, IPixel
+ {
+ provider.Configuration.MaxDegreeOfParallelism =
+ maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount;
+
+ FormattableString details = $"MDP{maxDegreeOfParallelism}";
+
+ provider.RunValidatingProcessorTest(
+ x => x.Resize(x.GetCurrentSize() / 2),
+ details,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+
[Theory]
[WithTestPatternImages(100, 100, DefaultPixelType)]
public void Resize_Compand(TestImageProvider provider)
@@ -75,16 +96,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage())
- {
- image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, true));
-
- image.DebugSave(provider);
- image.CompareToReferenceOutput(ValidatorComparer, provider);
- }
+ provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer);
}
-
[Theory]
[WithFileCollection(nameof(CommonTestImages), DefaultPixelType)]
public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider)
@@ -105,20 +119,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
}
-
[Theory]
- [WithFile(TestImages.Png.Kaboom, DefaultPixelType)]
- public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider)
+ [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)]
+ [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)]
+ public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage())
- {
- image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2));
- image.DebugSave(provider);
- image.CompareToReferenceOutput(ValidatorComparer, provider);
- }
- }
+ string details = compand ? "Compand" : "";
+ provider.RunValidatingProcessorTest(
+ x => x.Resize(x.GetCurrentSize() / 2, compand),
+ details,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+ }
+
[Theory]
[WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
public void Resize_IsAppliedToAllFrames(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
index 30ac0856c..5b5e4740a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests
public virtual string SourceFileOrDescription => "";
- public Configuration Configuration { get; set; } = Configuration.Default.Clone();
+ public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance();
///
/// Utility instance to provide informations about the test image & manage input/output
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
index 9eb051e7a..0b1b89cc0 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
@@ -1,4 +1,5 @@
using System;
+using System.Numerics;
namespace SixLabors.ImageSharp.Tests
{
@@ -10,7 +11,23 @@ namespace SixLabors.ImageSharp.Tests
for (int i = 0; i < length; i++)
{
- values[i] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal;
+ values[i] = GetRandomFloat(rnd, minVal, maxVal);
+ }
+
+ return values;
+ }
+
+ public static Vector4[] GenerateRandomVectorArray(this Random rnd, int length, float minVal, float maxVal)
+ {
+ var values = new Vector4[length];
+
+ for (int i = 0; i < length; i++)
+ {
+ ref Vector4 v = ref values[i];
+ v.X = GetRandomFloat(rnd, minVal, maxVal);
+ v.Y = GetRandomFloat(rnd, minVal, maxVal);
+ v.Z = GetRandomFloat(rnd, minVal, maxVal);
+ v.W = GetRandomFloat(rnd, minVal, maxVal);
}
return values;
@@ -28,5 +45,10 @@ namespace SixLabors.ImageSharp.Tests
return values;
}
+
+ private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
+ {
+ return (float)rnd.NextDouble() * (maxVal - minVal) + minVal;
+ }
}
}
\ No newline at end of file
diff --git a/tests/Images/External b/tests/Images/External
index c0627f384..03c7fa758 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit c0627f384c1d3d2f8d914c9578ae31354c35fd2c
+Subproject commit 03c7fa7582dea75cea0d49514ccb7e1b6dc9e780