Browse Source

expose limited Buffer2D internals, smplify API surface

pull/1035/head
Anton Firszov 6 years ago
parent
commit
1993facee2
  1. 2
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  2. 4
      src/ImageSharp/ImageFrame{TPixel}.cs
  3. 132
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  4. 18
      src/ImageSharp/Memory/Buffer2D{T}.cs
  5. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  6. 14
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  7. 2
      tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
  8. 2
      tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
  9. 22
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

2
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp
this.parent.GetConfiguration(),
source.Size(),
source.Metadata.DeepClone());
source.CopyPixelsTo(result.PixelBuffer.Span);
source.CopyPixelsTo(result.PixelBuffer.GetSpan());
return result;
}
}

4
src/ImageSharp/ImageFrame{TPixel}.cs

@ -218,10 +218,10 @@ namespace SixLabors.ImageSharp
if (typeof(TPixel) == typeof(TDestinationPixel))
{
Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination);
this.PixelBuffer.Span.CopyTo(dest1);
this.PixelBuffer.GetSpan().CopyTo(dest1);
}
PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.Span, destination);
PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.GetSpan(), destination);
}
/// <inheritdoc/>

132
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -13,13 +13,69 @@ namespace SixLabors.ImageSharp.Memory
/// <summary>
/// Defines extension methods for <see cref="Buffer2D{T}"/>.
/// </summary>
internal static class Buffer2DExtensions
public static class Buffer2DExtensions
{
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Span{T}"/> referencing the memory area.</returns>
public static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.GetSpan();
}
/// <summary>
/// Gets the <see cref="Memory{T}"/> holding the backing buffer of <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Memory{T}"/>.</returns>
public static Memory<T> GetMemory<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary>
public static unsafe void CopyColumns<T>(
internal static unsafe void CopyColumns<T>(
this Buffer2D<T> buffer,
int sourceIndex,
int destIndex,
@ -37,7 +93,7 @@ namespace SixLabors.ImageSharp.Memory
int dOffset = destIndex * elementSize;
long count = columnCount * elementSize;
Span<byte> span = MemoryMarshal.AsBytes(buffer.Memory.Span);
Span<byte> span = MemoryMarshal.AsBytes(buffer.GetMemory().Span);
fixed (byte* ptr = span)
{
@ -60,7 +116,7 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
internal static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
where T : struct
{
return new Rectangle(0, 0, buffer.Width, buffer.Height);
@ -73,11 +129,11 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct =>
new BufferArea<T>(buffer, rectangle);
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
@ -87,64 +143,17 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
where T : struct =>
new BufferArea<T>(buffer);
public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
/// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary>
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
internal static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
where T : struct
{
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="x">The x coordinate (position in the row)</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
where T : struct
{
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}
/// <summary>
@ -153,21 +162,12 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Size{T}"/> of the buffer</returns>
public static Size Size<T>(this Buffer2D<T> buffer)
internal static Size Size<T>(this Buffer2D<T> buffer)
where T : struct
{
return new Size(buffer.Width, buffer.Height);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// </summary>
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
return buffer.MemorySource.GetSpan();
}
[Conditional("DEBUG")]
private static void CheckColumnRegionsDoNotOverlap<T>(
Buffer2D<T> buffer,

18
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Memory
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal sealed class Buffer2D<T> : IDisposable
public sealed class Buffer2D<T> : IDisposable
where T : struct
{
private MemorySource<T> memorySource;
@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="memorySource">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
public Buffer2D(MemorySource<T> memorySource, int width, int height)
internal Buffer2D(MemorySource<T> memorySource, int width, int height)
{
this.memorySource = memorySource;
this.Width = width;
@ -44,11 +44,7 @@ namespace SixLabors.ImageSharp.Memory
/// <summary>
/// Gets the backing <see cref="MemorySource{T}"/>
/// </summary>
public MemorySource<T> MemorySource => this.memorySource;
public Memory<T> Memory => this.MemorySource.Memory;
public Span<T> Span => this.Memory.Span;
internal MemorySource<T> MemorySource => this.memorySource;
/// <summary>
/// Gets a reference to the element at the specified position.
@ -56,7 +52,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="x">The x coordinate (row)</param>
/// <param name="y">The y coordinate (position at row)</param>
/// <returns>A reference to the element.</returns>
public ref T this[int x, int y]
internal ref T this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@ -64,7 +60,7 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Span;
Span<T> span = this.GetSpan();
return ref span[(this.Width * y) + x];
}
}
@ -81,7 +77,7 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeGreaterThan(h, 0, nameof(h));
DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h));
Memory<T> slice = this.Memory.Slice(y * this.Width, h * this.Width);
Memory<T> slice = this.GetMemory().Slice(y * this.Width, h * this.Width);
var memory = new MemorySource<T>(slice);
return new Buffer2D<T>(memory, this.Width, h);
}
@ -98,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
/// </summary>
public static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
internal static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{
MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
SwapDimensionData(destination, source);

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.DestinationLength = destinationLength;
this.MaxDiameter = (radius * 2) + 1;
this.data = memoryAllocator.Allocate2D<float>(this.MaxDiameter, bufferHeight, AllocationOptions.Clean);
this.pinHandle = this.data.Memory.Pin();
this.pinHandle = this.data.GetMemory().Pin();
this.kernels = new ResizeKernel[destinationLength];
this.tempValues = new double[this.MaxDiameter];
}

14
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -115,6 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D<TPixel> destination)
{
Span<Vector4> tempColSpan = this.tempColumnBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan();
for (int y = rowInterval.Min; y < rowInterval.Max; y++)
{
@ -129,8 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan);
int top = kernel.StartIndex - this.currentWindow.Min;
ref Vector4 fpBase = ref this.transposedFirstPassBuffer.Span[top];
ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top];
for (int x = 0; x < this.destWidth; x++)
{
@ -167,6 +167,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private void CalculateFirstPassValues(RowInterval calculationInterval)
{
Span<Vector4> tempRowSpan = this.tempRowBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan();
for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetRowSpan(y);
@ -177,17 +179,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
tempRowSpan,
this.conversionModifiers);
// Span<Vector4> firstPassSpan = this.transposedFirstPassBuffer.Span.Slice(y - this.currentWindow.Min);
ref Vector4 firstPassBaseRef = ref this.transposedFirstPassBuffer.Span[y - this.currentWindow.Min];
// optimization for:
// Span<Vector4> firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min);
ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min];
for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++)
{
ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X);
// optimization for:
// firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan);
Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan);
}
}
}
}
}
}

2
tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs

@ -332,7 +332,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
});
// Assert:
TestImageExtensions.CompareBuffers(expected.Span, actual.Span);
TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan());
}
}

2
tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Span<int> span = buffer.GetMultiRowSpan(rows);
ref int expected0 = ref buffer.Span[min * width];
ref int expected0 = ref buffer.GetSpan()[min * width];
int expectedLength = (max - min) * width;
ref int actual0 = ref span[0];

22
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height);
Assert.Equal(width * height, buffer.Memory.Length);
Assert.Equal(width * height, buffer.GetMemory().Length);
}
}
@ -75,22 +75,6 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
}
[Theory]
[InlineData(7, 42, 0, 0)]
[InlineData(7, 42, 3, 10)]
[InlineData(17, 42, 0, 41)]
public void GetRowSpanXY(int width, int height, int x, int y)
{
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Span<TestStructs.Foo> span = buffer.GetRowSpan(x, y);
// Assert.Equal(width * y + x, span.Start);
Assert.Equal(width - x, span.Length);
Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y + x);
}
}
[Theory]
[InlineData(42, 8, 0, 0)]
[InlineData(400, 1000, 20, 10)]
@ -140,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(width, height))
{
rnd.RandomFill(b.Span, 0, 1);
rnd.RandomFill(b.GetSpan(), 0, 1);
b.CopyColumns(startIndex, destIndex, columnCount);
@ -162,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(100, 100))
{
rnd.RandomFill(b.Span, 0, 1);
rnd.RandomFill(b.GetSpan(), 0, 1);
b.CopyColumns(0, 50, 22);
b.CopyColumns(0, 50, 22);

Loading…
Cancel
Save