Browse Source

Merge remote-tracking branch 'upstream/master' into 718-gray8-gray16

af/merge-core
James Jackson-South 8 years ago
parent
commit
bc2eb8c87e
  1. 2
      src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs
  2. 3
      src/ImageSharp/ColorSpaces/Companding/LCompanding.cs
  3. 2
      src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs
  4. 2
      src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs
  5. 89
      src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs
  6. 1
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs
  7. 1
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs
  8. 1
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs
  9. 1
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs
  10. 35
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs
  11. 1
      src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs
  12. 1
      src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs
  13. 156
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  14. 124
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  15. 74
      src/ImageSharp/Common/Helpers/Vector4Utils.cs
  16. 18
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  17. 91
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  18. 59
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  19. 75
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  20. 6
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
  21. 10
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  22. 15
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  23. 9
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  24. 59
      tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs
  25. 49
      tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs
  26. 4
      tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
  27. 74
      tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs
  28. 44
      tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs
  29. 2
      tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaCompanding.cs → src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs

@ -4,7 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Companding
{ {
/// <summary> /// <summary>
/// Implements gamma companding /// Implements gamma companding

3
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LCompanding.cs → src/ImageSharp/ColorSpaces/Companding/LCompanding.cs

@ -3,8 +3,9 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Companding
{ {
/// <summary> /// <summary>
/// Implements L* companding /// Implements L* companding

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020Companding.cs → src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs

@ -4,7 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Companding
{ {
/// <summary> /// <summary>
/// Implements Rec. 2020 companding function (for 12-bits). /// Implements Rec. 2020 companding function (for 12-bits).

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709Companding.cs → src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs

@ -4,7 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Companding
{ {
/// <summary> /// <summary>
/// Implements the Rec. 709 companding function. /// Implements the Rec. 709 companding function.

89
src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs

@ -0,0 +1,89 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.ColorSpaces.Companding
{
/// <summary>
/// Implements sRGB companding
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public static class SRgbCompanding
{
/// <summary>
/// Expands the companded vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
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>
/// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
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>
/// Expands a companded vector to its linear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/> representing the linear channel values.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Expand(Vector4 vector) => new Vector4(Expand(vector.X), Expand(vector.Y), Expand(vector.Z), vector.W);
/// <summary>
/// Compresses an uncompanded vector (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The <see cref="Vector4"/> representing the nonlinear channel values.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Compress(Vector4 vector) => new Vector4(Compress(vector.X), Compress(vector.Y), Compress(vector.Z), vector.W);
/// <summary>
/// Expands a companded channel to its linear equivalent with respect to the energy.
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
}
}

1
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{ {

1
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{ {

1
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{ {

1
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{ {

35
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbCompanding.cs

@ -1,35 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{
/// <summary>
/// Implements sRGB companding
/// </summary>
/// <remarks>
/// For more info see:
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
/// </remarks>
public static class SRgbCompanding
{
/// <summary>
/// Expands a companded channel to its linear equivalent with respect to the energy.
/// </summary>
/// <param name="channel">The channel value</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="channel">The channel value</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
}
}

1
src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.ColorSpaces.Companding;
namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
{ {

1
src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.ColorSpaces.Companding;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming

156
src/ImageSharp/Common/Extensions/Vector4Extensions.cs

@ -1,156 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Vector4"/> struct.
/// </summary>
internal static class Vector4Extensions
{
/// <summary>
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Premultiply(this Vector4 source)
{
float w = source.W;
Vector4 premultiplied = source * w;
premultiplied.W = w;
return premultiplied;
}
/// <summary>
/// Reverses the result of premultiplying a vector via <see cref="Premultiply(Vector4)"/>.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 UnPremultiply(this Vector4 source)
{
float w = source.W;
Vector4 unpremultiplied = source / w;
unpremultiplied.W = w;
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)
{
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)
{
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"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="linear">The <see cref="Vector4"/> whose signal to compress.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Compress(this Vector4 linear)
{
// TODO: Is there a faster way to do this?
return new Vector4(
SRgbCompanding.Compress(linear.X),
SRgbCompanding.Compress(linear.Y),
SRgbCompanding.Compress(linear.Z),
linear.W);
}
/// <summary>
/// Expands an sRGB color signal to its linear equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
/// <see href="http://entropymine.com/imageworsener/srgbformula/"/>
/// </summary>
/// <param name="gamma">The <see cref="Rgba32"/> whose signal to expand.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Expand(this Vector4 gamma)
{
// TODO: Is there a faster way to do this?
return new Vector4(
SRgbCompanding.Expand(gamma.X),
SRgbCompanding.Expand(gamma.Y),
SRgbCompanding.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 = SRgbCompanding.Compress(v.X);
v.Y = SRgbCompanding.Compress(v.Y);
v.Z = SRgbCompanding.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 = SRgbCompanding.Expand(v.X);
v.Y = SRgbCompanding.Expand(v.Y);
v.Z = SRgbCompanding.Expand(v.Z);
}
}
}
}

124
src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs

@ -0,0 +1,124 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for <see cref="DenseMatrix{T}"/>.
/// </summary>
internal static class DenseMatrixUtils
{
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int row,
int column,
int maxRow,
int maxColumn,
int offsetColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + offsetColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(0, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
}
}
ref Vector4 target = ref targetRow[column];
vector.W = target.W;
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the two kernel weight values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrixY">The vertical dense matrix.</param>
/// <param name="matrixX">The horizontal dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve2D<TPixel>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int row,
int column,
int maxRow,
int maxColumn,
int offsetColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vectorY = default;
Vector4 vectorX = default;
int matrixHeight = matrixY.Rows;
int matrixWidth = matrixY.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + offsetColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(0, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
vectorX += matrixX[y, x] * currentColor;
vectorY += matrixY[y, x] * currentColor;
}
}
var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
ref Vector4 target = ref targetRow[column];
vector.W = target.W;
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
}
}

74
src/ImageSharp/Common/Helpers/Vector4Utils.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Utility methods for the <see cref="Vector4"/> struct.
/// </summary>
internal static class Vector4Utils
{
/// <summary>
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Premultiply(ref Vector4 source)
{
float w = source.W;
source *= w;
source.W = w;
}
/// <summary>
/// Reverses the result of premultiplying a vector via <see cref="Premultiply(ref Vector4)"/>.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void UnPremultiply(ref Vector4 source)
{
float w = source.W;
source /= w;
source.W = w;
}
/// <summary>
/// Bulk variant of <see cref="Premultiply(ref Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
[MethodImpl(InliningOptions.ShortMethod)]
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);
Premultiply(ref v);
}
}
/// <summary>
/// Bulk variant of <see cref="UnPremultiply(ref Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
[MethodImpl(InliningOptions.ShortMethod)]
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);
UnPremultiply(ref v);
}
}
}
}

18
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Primitives namespace SixLabors.ImageSharp.Primitives
{ {
@ -31,6 +32,11 @@ namespace SixLabors.ImageSharp.Primitives
/// </summary> /// </summary>
public readonly int Rows; public readonly int Rows;
/// <summary>
/// Gets the size of the dense matrix.
/// </summary>
public readonly Size Size;
/// <summary> /// <summary>
/// Gets the number of items in the array. /// Gets the number of items in the array.
/// </summary> /// </summary>
@ -57,6 +63,7 @@ namespace SixLabors.ImageSharp.Primitives
this.Rows = rows; this.Rows = rows;
this.Columns = columns; this.Columns = columns;
this.Size = new Size(columns, rows);
this.Count = columns * rows; this.Count = columns * rows;
this.Data = new T[this.Columns * this.Rows]; this.Data = new T[this.Columns * this.Rows];
} }
@ -76,6 +83,7 @@ namespace SixLabors.ImageSharp.Primitives
this.Rows = rows; this.Rows = rows;
this.Columns = columns; this.Columns = columns;
this.Size = new Size(columns, rows);
this.Count = this.Columns * this.Rows; this.Count = this.Columns * this.Rows;
this.Data = new T[this.Columns * this.Rows]; this.Data = new T[this.Columns * this.Rows];
@ -182,13 +190,13 @@ namespace SixLabors.ImageSharp.Primitives
} }
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(DenseMatrix<T> other) => public override bool Equals(object obj) => obj is DenseMatrix<T> other && this.Equals(other);
this.Columns == other.Columns &&
this.Rows == other.Rows &&
this.Span.SequenceEqual(other.Span);
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => obj is DenseMatrix<T> other && this.Equals(other); public bool Equals(DenseMatrix<T> other) =>
this.Columns == other.Columns
&& this.Rows == other.Rows
&& this.Span.SequenceEqual(other.Span);
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() => this.Data.GetHashCode(); public override int GetHashCode() => this.Data.GetHashCode();

91
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs

@ -3,8 +3,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -27,6 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
public Convolution2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY) public Convolution2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY)
{ {
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX; this.KernelX = kernelX;
this.KernelY = kernelY; this.KernelY = kernelY;
} }
@ -47,89 +46,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Rectangle sourceRectangle, Rectangle sourceRectangle,
Configuration configuration) Configuration configuration)
{ {
int kernelYHeight = this.KernelY.Rows; DenseMatrix<float> matrixY = this.KernelY;
int kernelYWidth = this.KernelY.Columns; DenseMatrix<float> matrixX = this.KernelX;
int kernelXHeight = this.KernelX.Rows;
int kernelXWidth = this.KernelX.Columns; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int radiusY = kernelYHeight >> 1; int startY = interest.Y;
int radiusX = kernelXWidth >> 1; int endY = interest.Bottom;
int startX = interest.X;
int startY = sourceRectangle.Y; int endX = interest.Right;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
int maxY = endY - 1; int maxY = endY - 1;
int maxX = endX - 1; int maxX = endX - 1;
using (Buffer2D<TPixel> targetPixels = using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
{ {
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width;
ParallelHelper.IterateRows( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle, workingRectangle,
configuration, configuration,
rows => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = source.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y); PixelOperations<TPixel>.Instance.ToVector4(targetRowSpan, vectorSpan, length);
for (int x = startX; x < endX; x++) for (int x = 0; x < width; x++)
{ {
float rX = 0; DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
float gX = 0;
float bX = 0;
float rY = 0;
float gY = 0;
float bY = 0;
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelYHeight; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY);
for (int fx = 0; fx < kernelXWidth; fx++)
{
int fxr = fx - radiusX;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
if (fy < kernelXHeight)
{
Vector4 kx = this.KernelX[fy, fx] * currentColor;
rX += kx.X;
gX += kx.Y;
bX += kx.Z;
}
if (fx < kernelYWidth)
{
Vector4 ky = this.KernelY[fy, fx] * currentColor;
rY += ky.X;
gY += ky.Y;
bY += ky.Z;
}
}
}
float red = MathF.Sqrt((rX * rX) + (rY * rY));
float green = MathF.Sqrt((gX * gX) + (gY * gY));
float blue = MathF.Sqrt((bX * bX) + (bY * bY));
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(
new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
} }
PixelOperations<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
} }
}); });

59
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -3,14 +3,11 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@ -48,8 +45,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
{ {
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, configuration); source.CopyTo(firstPassPixels);
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, configuration);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration);
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration);
} }
} }
@ -68,14 +68,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels, Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels, Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle, Rectangle sourceRectangle,
DenseMatrix<float> kernel, // TODO: Can't use 'in' as pass by ref to lambda expression. in DenseMatrix<float> kernel,
Configuration configuration) Configuration configuration)
{ {
int kernelHeight = kernel.Rows; DenseMatrix<float> matrix = kernel;
int kernelWidth = kernel.Columns;
int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1;
int startY = sourceRectangle.Y; int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom; int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
@ -84,44 +80,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int maxX = endX - 1; int maxX = endX - 1;
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width;
ParallelHelper.IterateRows( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle, workingRectangle,
configuration, configuration,
rows => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRow = targetPixels.GetRowSpan(y); Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(targetRowSpan, vectorSpan, length);
for (int x = startX; x < endX; x++) for (int x = 0; x < width; x++)
{ {
Vector4 destination = default; DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX);
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelHeight; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> row = sourcePixels.GetRowSpan(offsetY);
for (int fx = 0; fx < kernelWidth; fx++)
{
int fxr = fx - radiusX;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = row[offsetX].ToVector4().Premultiply();
destination += kernel[fy, fx] * currentColor;
}
}
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination.UnPremultiply());
} }
PixelOperations<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
} }
}); });
} }

75
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs

@ -3,14 +3,10 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@ -26,10 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="ConvolutionProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ConvolutionProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param> /// <param name="kernelXY">The 2d gradient operator.</param>
public ConvolutionProcessor(DenseMatrix<float> kernelXY) public ConvolutionProcessor(DenseMatrix<float> kernelXY) => this.KernelXY = kernelXY;
{
this.KernelXY = kernelXY;
}
/// <summary> /// <summary>
/// Gets the 2d gradient operator. /// Gets the 2d gradient operator.
@ -39,13 +32,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{ {
int kernelLength = this.KernelXY.Rows; DenseMatrix<float> matrix = this.KernelXY;
int radius = kernelLength >> 1; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int startY = sourceRectangle.Y; int endY = interest.Bottom;
int endY = sourceRectangle.Bottom; int startX = interest.X;
int startX = sourceRectangle.X; int endX = interest.Right;
int endX = sourceRectangle.Right;
int maxY = endY - 1; int maxY = endY - 1;
int maxX = endX - 1; int maxX = endX - 1;
@ -53,53 +45,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width;
ParallelHelper.IterateRows( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRect, workingRectangle,
configuration, configuration,
rows => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = source.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y); PixelOperations<TPixel>.Instance.ToVector4(targetRowSpan, vectorSpan, length);
for (int x = startX; x < endX; x++) for (int x = 0; x < width; x++)
{ {
float red = 0; DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
float green = 0;
float blue = 0;
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelLength; fy++)
{
int fyr = fy - radius;
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY);
for (int fx = 0; fx < kernelLength; fx++)
{
int fxr = fx - radius;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply();
currentColor *= this.KernelXY[fy, fx];
red += currentColor.X;
green += currentColor.Y;
blue += currentColor.Z;
}
}
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(
new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply());
} }
PixelOperations<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
} }
}); });

6
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs

@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param> /// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetector2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY, bool grayscale) protected EdgeDetector2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY, bool grayscale)
{ {
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX; this.KernelX = kernelX;
this.KernelY = kernelY; this.KernelY = kernelY;
this.Grayscale = grayscale; this.Grayscale = grayscale;
@ -42,10 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool Grayscale { get; set; } public bool Grayscale { get; set; }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
{
new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)

10
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -7,7 +7,6 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
@ -207,18 +206,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int xx = 0, i = minX; i <= maxX; i++, xx++) for (int xx = 0, i = minX; i <= maxX; i++, xx++)
{ {
float xWeight = Unsafe.Add(ref xSpanRef, xx); float xWeight = Unsafe.Add(ref xSpanRef, xx);
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels // Values are first premultiplied to prevent darkening of edge pixels
Vector4 multiplied = vector.Premultiply(); var current = source[i, j].ToVector4();
sum += multiplied * xWeight * yWeight; Vector4Utils.Premultiply(ref current);
sum += current * xWeight * yWeight;
} }
} }
ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
// Reverse the premultiplication // Reverse the premultiplication
dest.PackFromVector4(sum.UnPremultiply()); Vector4Utils.UnPremultiply(ref sum);
dest.PackFromVector4(sum);
} }
} }
}); });

15
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -7,7 +7,6 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
@ -216,18 +215,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int xx = 0, i = minX; i <= maxX; i++, xx++) for (int xx = 0, i = minX; i <= maxX; i++, xx++)
{ {
float xWeight = Unsafe.Add(ref xSpanRef, xx); float xWeight = Unsafe.Add(ref xSpanRef, xx);
var vector = source[i, j].ToVector4();
// Values are first premultiplied to prevent darkening of edge pixels // Values are first premultiplied to prevent darkening of edge pixels
Vector4 multiplied = vector.Premultiply(); var current = source[i, j].ToVector4();
sum += multiplied * xWeight * yWeight; Vector4Utils.Premultiply(ref current);
sum += current * xWeight * yWeight;
} }
} }
ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); ref TPixel dest = ref Unsafe.Add(ref destRowRef, x);
// Reverse the premultiplication // Reverse the premultiplication
dest.PackFromVector4(sum.UnPremultiply()); Vector4Utils.UnPremultiply(ref sum);
dest.PackFromVector4(sum);
} }
} }
}); });
@ -242,9 +242,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns> /// <returns>
/// The <see cref="Matrix4x4"/>. /// The <see cref="Matrix4x4"/>.
/// </returns> /// </returns>
protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) => this.TransformMatrix;
{
return this.TransformMatrix;
}
} }
} }

9
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ColorSpaces.Companding;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -257,13 +258,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Span<Vector4> tempRowSpan = tempRowBuffer.Span; Span<Vector4> tempRowSpan = tempRowBuffer.Span;
PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length);
Vector4Extensions.Premultiply(tempRowSpan); Vector4Utils.Premultiply(tempRowSpan);
ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y];
if (this.Compand) if (this.Compand)
{ {
Vector4Extensions.Expand(tempRowSpan); SRgbCompanding.Expand(tempRowSpan);
} }
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
@ -300,11 +301,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY); Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY);
} }
Vector4Extensions.UnPremultiply(tempRowSpan); Vector4Utils.UnPremultiply(tempRowSpan);
if (this.Compand) if (this.Compand)
{ {
Vector4Extensions.Compress(tempRowSpan); SRgbCompanding.Compress(tempRowSpan);
} }
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);

59
tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs

@ -0,0 +1,59 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization
{
public class Premultiply
{
[Benchmark(Baseline = true)]
public Vector4 PremultiplyByVal()
{
var input = new Vector4(.5F);
return Vector4Utils.Premultiply(input);
}
[Benchmark]
public Vector4 PremultiplyByRef()
{
var input = new Vector4(.5F);
Vector4Utils.PremultiplyRef(ref input);
return input;
}
[Benchmark]
public Vector4 PremultiplyRefWithPropertyAssign()
{
var input = new Vector4(.5F);
Vector4Utils.PremultiplyRefWithPropertyAssign(ref input);
return input;
}
}
internal static class Vector4Utils
{
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 Premultiply(Vector4 source)
{
float w = source.W;
Vector4 premultiplied = source * w;
premultiplied.W = w;
return premultiplied;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void PremultiplyRef(ref Vector4 source)
{
float w = source.W;
source *= w;
source.W = w;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void PremultiplyRefWithPropertyAssign(ref Vector4 source)
{
float w = source.W;
source *= new Vector4(w) { W = 1 };
}
}
}

49
tests/ImageSharp.Tests/Colorspaces/Conversion/CompandingTests.cs → tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs

@ -1,10 +1,13 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; using System;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces.Companding;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding
{ {
/// <summary> /// <summary>
/// Tests various companding algorithms. Numbers are hand calculated from formulas online. /// Tests various companding algorithms. Numbers are hand calculated from formulas online.
@ -16,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F);
[Fact] [Fact]
public void Rec2020CompandingIsCorrect() public void Rec2020Companding_IsCorrect()
{ {
const float input = .667F; const float input = .667F;
float e = Rec2020Companding.Expand(input); float e = Rec2020Companding.Expand(input);
@ -25,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
} }
[Fact] [Fact]
public void Rec709CompandingIsCorrect() public void Rec709Companding_IsCorrect()
{ {
const float input = .667F; const float input = .667F;
float e = Rec709Companding.Expand(input); float e = Rec709Companding.Expand(input);
@ -34,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
} }
[Fact] [Fact]
public void SRgbCompandingIsCorrect() public void SRgbCompanding_IsCorrect()
{ {
const float input = .667F; const float input = .667F;
float e = SRgbCompanding.Expand(input); float e = SRgbCompanding.Expand(input);
@ -42,8 +45,38 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
CompandingIsCorrectImpl(e, c, .40242353F, .667F); CompandingIsCorrectImpl(e, c, .40242353F, .667F);
} }
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(30)]
public void SRgbCompanding_Expand_VectorSpan(int length)
{
var rnd = new Random(42);
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
Vector4[] expected = source.Select(v => SRgbCompanding.Expand(v)).ToArray();
SRgbCompanding.Expand(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(30)]
public void SRgbCompanding_Compress_VectorSpan(int length)
{
var rnd = new Random(42);
Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1);
Vector4[] expected = source.Select(v => SRgbCompanding.Compress(v)).ToArray();
SRgbCompanding.Compress(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
}
[Fact] [Fact]
public void GammaCompandingIsCorrect() public void GammaCompanding_IsCorrect()
{ {
const float gamma = 2.2F; const float gamma = 2.2F;
const float input = .667F; const float input = .667F;
@ -53,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
} }
[Fact] [Fact]
public void LCompandingIsCorrect() public void LCompanding_IsCorrect()
{ {
const float input = .667F; const float input = .667F;
float e = LCompanding.Expand(input); float e = LCompanding.Expand(input);
@ -67,4 +100,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
Assert.Equal(compressed, c, FloatComparer); Assert.Equal(compressed, c, FloatComparer);
} }
} }
} }

4
tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs

@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{ {
// it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color) // it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color)
TPixel colorOnDiagonal = image[i, i]; TPixel colorOnDiagonal = image[i, i];
// TODO: This is incorrect. from -0 to < 0 ?? // TODO: This is incorrect. from -0 to < 0 ??
int orthoCount = 0; int orthoCount = 0;
for (int offset = -orthoCount; offset < orthoCount; offset++) for (int offset = -orthoCount; offset < orthoCount; offset++)
@ -322,11 +322,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var coloringVariant = new StringBuilder(); var coloringVariant = new StringBuilder();
var colorStops = new ColorStop<TPixel>[stopPositions.Length]; var colorStops = new ColorStop<TPixel>[stopPositions.Length];
for (int i = 0; i < stopPositions.Length; i++) for (int i = 0; i < stopPositions.Length; i++)
{ {
TPixel color = colors[stopColorCodes[i % colors.Length]]; TPixel color = colors[stopColorCodes[i % colors.Length]];
float position = stopPositions[i]; float position = stopPositions[i];
colorStops[i] = new ColorStop<TPixel>(position, color); colorStops[i] = new ColorStop<TPixel>(position, color);
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToRgba32().ToHex(), position); coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color.ToRgba32().ToHex(), position);
} }

74
tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs

@ -1,74 +0,0 @@
// 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));
}
}
}

44
tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs

@ -0,0 +1,44 @@
// 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 Vector4UtilsTests
{
[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 => { Vector4Utils.Premultiply(ref v); return v; }).ToArray();
Vector4Utils.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 => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray();
Vector4Utils.UnPremultiply(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
}
}
}

2
tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs

@ -3,6 +3,7 @@
using System; using System;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Primitives namespace SixLabors.ImageSharp.Tests.Primitives
@ -59,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives
Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0));
Assert.Equal(3, dense.Columns); Assert.Equal(3, dense.Columns);
Assert.Equal(2, dense.Rows); Assert.Equal(2, dense.Rows);
Assert.Equal(new Size(3, 2), dense.Size);
} }
[Fact] [Fact]

Loading…
Cancel
Save