Browse Source

better separation + optimized row processing

af/merge-core
Anton Firszov 8 years ago
parent
commit
75ec80a803
  1. 70
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  2. 39
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  3. 14
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  4. 33
      tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
  5. 76
      tests/ImageSharp.Tests/Helpers/Vector4ExtensionsTests.cs
  6. 12
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

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

@ -43,6 +43,42 @@ namespace SixLabors.ImageSharp
return unpremultiplied; 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> /// <summary>
/// Compresses a linear color signal to its sRGB equivalent. /// Compresses a linear color signal to its sRGB equivalent.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/> /// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>
@ -71,6 +107,40 @@ namespace SixLabors.ImageSharp
return new Vector4(Expand(gamma.X), Expand(gamma.Y), Expand(gamma.Z), gamma.W); 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> /// <summary>
/// Gets the compressed sRGB value from an linear signal. /// Gets the compressed sRGB value from an linear signal.
/// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/> /// <see href="http://www.4p8.com/eric.brasseur/gamma.html#formulas"/>

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

@ -278,44 +278,5 @@ namespace SixLabors.ImageSharp
return GetBoundingRectangle(topLeft, bottomRight); return GetBoundingRectangle(topLeft, bottomRight);
} }
/// <summary>
/// Pre-multiply all vectors.
/// "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
/// </summary>
/// <seealso cref="Vector4Extensions.Premultiply"/>
/// <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>
/// Revers <see cref="Premultiply"/>
/// </summary>
/// <seealso cref="Vector4Extensions.UnPremultiply"/>
/// <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;
}
}
} }
} }

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

@ -229,11 +229,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
int sourceHeight = source.Height;
// Interpolate the image using the calculated weights. // Interpolate the image using the calculated weights.
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // 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 // First process the columns. Since we are not using multiple threads startY and endY
// are the upper and lower bounds of the source rectangle. // are the upper and lower bounds of the source rectangle.
using (Buffer2D<Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D<Vector4>(source.Height, width)) using (Buffer2D<Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D<Vector4>(sourceHeight, width))
{ {
firstPassPixelsTransposed.MemorySource.Clear(); firstPassPixelsTransposed.MemorySource.Clear();
@ -250,14 +252,18 @@ 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);
ImageMaths.Premultiply(tempRowSpan); Vector4Extensions.Premultiply(tempRowSpan);
ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y];
if (this.Compand) if (this.Compand)
{ {
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX];
firstPassPixelsTransposed[y, x] = window.ConvolveExpand(tempRowSpan, sourceX).UnPremultiply();
Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) =
window.ConvolveExpand(tempRowSpan, sourceX).UnPremultiply();
} }
} }
else else
@ -265,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX];
firstPassPixelsTransposed[y, x] = Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) =
window.Convolve(tempRowSpan, sourceX); window.Convolve(tempRowSpan, sourceX);
} }
} }

33
tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs

@ -1,10 +1,6 @@
// 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 System;
using System.Linq;
using System.Numerics;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers namespace SixLabors.ImageSharp.Tests.Helpers
@ -39,35 +35,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
[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();
ImageMaths.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();
ImageMaths.UnPremultiply(source);
Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
}
// TODO: We need to test all ImageMaths methods! // 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));
}
}
}

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

@ -144,6 +144,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
} }
} }
[Theory]
[WithFile(TestImages.Png.Kaboom, DefaultPixelType)]
public void Resize_Compand_DoesNotBleedAlphaPixels<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);
}
}
[Theory] [Theory]
[WithFile(TestImages.Gif.Giphy, DefaultPixelType)] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
public void Resize_IsAppliedToAllFrames<TPixel>(TestImageProvider<TPixel> provider) public void Resize_IsAppliedToAllFrames<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save