Browse Source

Merge branch 'master' into 718-gray8-gray16

af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
be5013553e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 83
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  2. 26
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  3. 130
      src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs
  4. 95
      src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs
  5. 179
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  6. 55
      src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
  7. 154
      src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
  8. 123
      tests/ImageSharp.Benchmarks/Samplers/Resize.cs
  9. 41
      tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
  10. 76
      tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs
  11. 61
      tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs
  12. 56
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
  13. 57
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  14. 2
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  15. 24
      tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs
  16. 2
      tests/Images/External

83
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
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Premultiply(this Vector4 source)
{
float w = source.W;
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[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;
}
/// <summary>
/// Bulk variant of <see cref="Premultiply(System.Numerics.Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
public static void Premultiply(Span<Vector4> vectors)
{
// TODO: This method can be AVX2 optimized using Vector<float>
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;
}
}
/// <summary>
/// Bulk variant of <see cref="UnPremultiply(System.Numerics.Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
public static void UnPremultiply(Span<Vector4> vectors)
{
// TODO: This method can be AVX2 optimized using Vector<float>
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;
}
}
/// <summary>
/// Compresses a linear color signal to its sRGB equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
@ -49,7 +86,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="linear">The <see cref="Vector4"/> whose signal to compress.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[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
/// </summary>
/// <param name="gamma">The <see cref="Rgba32"/> whose signal to expand.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[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);
}
/// <summary>
/// Bulk variant of <see cref="Compress(System.Numerics.Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
public static void Compress(Span<Vector4> 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);
}
}
/// <summary>
/// Bulk variant of <see cref="Expand(System.Numerics.Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
public static void Expand(Span<Vector4> 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);
}
}
/// <summary>
/// Gets the compressed sRGB value from an linear signal.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
@ -79,7 +150,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
private static float Compress(float signal)
{
if (signal <= 0.0031308F)
@ -99,7 +170,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
private static float Expand(float signal)
{
if (signal <= 0.04045F)

26
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -80,6 +81,31 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort UpscaleFrom8BitTo16Bit(byte component) => (ushort)(component * 257);
/// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary>
public static int GreatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/// <summary>
/// Determine the Least Common Multiple (LCM) of two numbers.
/// TODO: This method might be useful for building a more compact <see cref="Processing.Processors.Transforms.KernelMap"/>
/// </summary>
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;
}
/// <summary>
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
/// </summary>

130
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
{
/// <summary>
/// Holds the <see cref="ResizeKernel"/> values in an optimized contigous memory region.
/// </summary>
internal class KernelMap : IDisposable
{
private readonly Buffer2D<float> data;
/// <summary>
/// Initializes a new instance of the <see cref="KernelMap"/> class.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for allocations.</param>
/// <param name="destinationSize">The size of the destination window</param>
/// <param name="kernelRadius">The radius of the kernel</param>
public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius)
{
int width = (int)Math.Ceiling(kernelRadius * 2);
this.data = memoryAllocator.Allocate2D<float>(width, destinationSize, AllocationOptions.Clean);
this.Kernels = new ResizeKernel[destinationSize];
}
/// <summary>
/// Gets the calculated <see cref="Kernels"/> values.
/// </summary>
public ResizeKernel[] Kernels { get; }
/// <summary>
/// Disposes <see cref="KernelMap"/> instance releasing it's backing buffer.
/// </summary>
public void Dispose()
{
this.data.Dispose();
}
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="sampler">The <see cref="IResampler"/></param>
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param>
/// <returns>The <see cref="KernelMap"/></returns>
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;
}
/// <summary>
/// Slices a weights value at the given positions.
/// </summary>
/// <param name="destIdx">The index in destination buffer</param>
/// <param name="leftIdx">The local left index value</param>
/// <param name="rightIdx">The local right index value</param>
/// <returns>The weights</returns>
private ResizeKernel CreateKernel(int destIdx, int leftIdx, int rightIdx)
{
return new ResizeKernel(destIdx, leftIdx, this.data, rightIdx - leftIdx + 1);
}
}
}

95
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
{
/// <summary>
/// Points to a collection of of weights allocated in <see cref="KernelMap"/>.
/// </summary>
internal struct ResizeKernel
{
/// <summary>
/// The local left index position
/// </summary>
public int Left;
/// <summary>
/// The length of the weights window
/// </summary>
public int Length;
/// <summary>
/// The buffer containing the weights values.
/// </summary>
private readonly Memory<float> buffer;
/// <summary>
/// Initializes a new instance of the <see cref="ResizeKernel"/> struct.
/// </summary>
/// <param name="index">The destination index in the buffer</param>
/// <param name="left">The local left index</param>
/// <param name="buffer">The span</param>
/// <param name="length">The length of the window</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel(int index, int left, Buffer2D<float> buffer, int length)
{
int flatStartIndex = index * buffer.Width;
this.Left = left;
this.buffer = buffer.MemorySource.Memory.Slice(flatStartIndex, length);
this.Length = length;
}
/// <summary>
/// Gets a reference to the first item of the window.
/// </summary>
/// <returns>The reference to the first item of the window</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ref float GetStartReference()
{
Span<float> span = this.buffer.Span;
return ref span[0];
}
/// <summary>
/// Gets the span representing the portion of the <see cref="KernelMap"/> that this window covers
/// </summary>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<float> GetSpan() => this.buffer.Span;
/// <summary>
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="ResizeKernel"/> instance.
/// </summary>
/// <param name="rowSpan">The input span of vectors</param>
/// <param name="sourceX">The source row position.</param>
/// <returns>The weighted sum</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span<Vector4> 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;
}
}
}

179
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<TPixel>
{
// The following fields are not immutable but are optionally created on demand.
private WeightsBuffer horizontalWeights;
private WeightsBuffer verticalWeights;
private KernelMap horizontalKernelMap;
private KernelMap verticalKernelMap;
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor{TPixel}"/> class.
@ -148,76 +147,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
public bool Compand { get; }
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param>
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <returns>The <see cref="WeightsBuffer"/></returns>
// 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;
}
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> 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<Vector4> firstPassPixels = source.MemoryAllocator.Allocate2D<Vector4>(width, source.Height))
using (Buffer2D<Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D<Vector4>(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<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<Vector4> tempRowSpan = tempRowBuffer.Span;
PixelOperations<TPixel>.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<Vector4>(
processRowsRect,
configuration,
rows =>
(rows, tempRowBuffer) =>
{
Span<Vector4> 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<Vector4> 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<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.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;
}
}
}

55
src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs

@ -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
{
/// <summary>
/// Holds the <see cref="WeightsWindow"/> values in an optimized contigous memory region.
/// </summary>
internal class WeightsBuffer : IDisposable
{
private readonly Buffer2D<float> dataBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for allocations.</param>
/// <param name="sourceSize">The size of the source window</param>
/// <param name="destinationSize">The size of the destination window</param>
public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize)
{
this.dataBuffer = memoryAllocator.Allocate2D<float>(sourceSize, destinationSize, AllocationOptions.Clean);
this.Weights = new WeightsWindow[destinationSize];
}
/// <summary>
/// Gets the calculated <see cref="Weights"/> values.
/// </summary>
public WeightsWindow[] Weights { get; }
/// <summary>
/// Disposes <see cref="WeightsBuffer"/> instance releasing it's backing buffer.
/// </summary>
public void Dispose()
{
this.dataBuffer.Dispose();
}
/// <summary>
/// Slices a weights value at the given positions.
/// </summary>
/// <param name="destIdx">The index in destination buffer</param>
/// <param name="leftIdx">The local left index value</param>
/// <param name="rightIdx">The local right index value</param>
/// <returns>The weights</returns>
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
{
return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
}
}
}

154
src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs

@ -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
{
/// <summary>
/// Points to a collection of of weights allocated in <see cref="WeightsBuffer"/>.
/// </summary>
internal struct WeightsWindow
{
/// <summary>
/// The local left index position
/// </summary>
public int Left;
/// <summary>
/// The length of the weights window
/// </summary>
public int Length;
/// <summary>
/// The index in the destination buffer
/// </summary>
private readonly int flatStartIndex;
/// <summary>
/// The buffer containing the weights values.
/// </summary>
private readonly MemorySource<float> buffer;
/// <summary>
/// Initializes a new instance of the <see cref="WeightsWindow"/> struct.
/// </summary>
/// <param name="index">The destination index in the buffer</param>
/// <param name="left">The local left index</param>
/// <param name="buffer">The span</param>
/// <param name="length">The length of the window</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal WeightsWindow(int index, int left, Buffer2D<float> buffer, int length)
{
this.flatStartIndex = (index * buffer.Width) + left;
this.Left = left;
this.buffer = buffer.MemorySource;
this.Length = length;
}
/// <summary>
/// Gets a reference to the first item of the window.
/// </summary>
/// <returns>The reference to the first item of the window</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref float GetStartReference()
{
Span<float> span = this.buffer.GetSpan();
return ref span[this.flatStartIndex];
}
/// <summary>
/// Gets the span representing the portion of the <see cref="WeightsBuffer"/> that this window covers
/// </summary>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<float> GetWindowSpan() => this.buffer.GetSpan().Slice(this.flatStartIndex, this.Length);
/// <summary>
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
/// </summary>
/// <param name="rowSpan">The input span of vectors</param>
/// <param name="sourceX">The source row position.</param>
/// <returns>The weighted sum</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedRowSum(Span<Vector4> 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;
}
/// <summary>
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
/// Applies <see cref="Vector4Extensions.Expand(float)"/> to all input vectors.
/// </summary>
/// <param name="rowSpan">The input span of vectors</param>
/// <param name="sourceX">The source row position.</param>
/// <returns>The weighted sum</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeExpandedWeightedRowSum(Span<Vector4> 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();
}
/// <summary>
/// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x',
/// weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
/// </summary>
/// <param name="firstPassPixels">The buffer of input vectors in row first order</param>
/// <param name="x">The row position</param>
/// <param name="sourceY">The source column position.</param>
/// <returns>The weighted sum</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedColumnSum(Buffer2D<Vector4> 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();
}
}
}

123
tests/ImageSharp.Benchmarks/Samplers/Resize.cs

@ -1,7 +1,5 @@
// <copyright file="Resize.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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<Rgba32> 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<Rgba32>(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<Rgba32>(this.configuration, 2000, 2000))
this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism;
using (Image<Rgba32> 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<RgbaVector> image = new Image<RgbaVector>(2000, 2000))
// {
// image.Resize(400, 400);
// return new CoreSize(image.Width, image.Height);
// }
//}
//[Benchmark(Description = "ImageSharp Compand Resize")]
//public CoreSize ResizeCoreCompand()
//{
// using (Image<Rgba32> image = new Image<Rgba32>(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<RgbaVector> image = new Image<RgbaVector>(2000, 2000))
// {
// image.Resize(400, 400, true);
// return new CoreSize(image.Width, image.Height);
// }
//}
protected abstract void ExecuteResizeOperation(IImageProcessingContext<Rgba32> ctx);
}
public class Resize_Bicubic : ResizeBenchmarkBase
{
protected override void ExecuteResizeOperation(IImageProcessingContext<Rgba32> ctx)
{
ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic);
}
}
public class Resize_BicubicCompand : ResizeBenchmarkBase
{
protected override void ExecuteResizeOperation(IImageProcessingContext<Rgba32> ctx)
{
ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic, true);
}
}
}

41
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!
}
}

76
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));
}
}
}

61
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<float> 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());
}
}
}

56
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<Rgba32>(width, height))
using (var image = new Image<Rgba32>(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<Rgba32>(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<float> 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());
}
}
}

57
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<string, IResampler> AllReSamplers =
new TheoryData<string, IResampler>
{
@ -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<TPixel>(TestImageProvider<TPixel> provider, int maxDegreeOfParallelism)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel>(TestImageProvider<TPixel> provider)
@ -75,16 +96,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public void Resize_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider)
@ -105,20 +119,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
}
[Theory]
[WithFile(TestImages.Png.Kaboom, DefaultPixelType)]
public void Resize_DoesNotBleedAlphaPixels<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)]
[WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)]
public void Resize_DoesNotBleedAlphaPixels<TPixel>(TestImageProvider<TPixel> provider, bool compand)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider)

2
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();
/// <summary>
/// Utility instance to provide informations about the test image & manage input/output

24
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;
}
}
}

2
tests/Images/External

@ -1 +1 @@
Subproject commit c0627f384c1d3d2f8d914c9578ae31354c35fd2c
Subproject commit 03c7fa7582dea75cea0d49514ccb7e1b6dc9e780
Loading…
Cancel
Save