Browse Source

hide Buffer<T>.Array, use IManagedByteBuffer when necessary

af/merge-core
Anton Firszov 8 years ago
parent
commit
17018555c5
  1. 4
      src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
  2. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 21
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 19
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  6. 21
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs
  8. 14
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  9. 2
      src/ImageSharp/Formats/Png/PngChunk.cs
  10. 71
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  11. 71
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  12. 4
      src/ImageSharp/Image/Image.Decode.cs
  13. 12
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  14. 41
      src/ImageSharp/Image/PixelArea{TPixel}.cs
  15. 2
      src/ImageSharp/Memory/ArrayPoolMemoryManager.cs
  16. 1
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  17. 4
      src/ImageSharp/Memory/Buffer2D{T}.cs
  18. 54
      src/ImageSharp/Memory/BufferExtensions.cs
  19. 87
      src/ImageSharp/Memory/Buffer{T}.cs
  20. 2
      src/ImageSharp/Memory/ManagedByteBuffer.cs
  21. 2
      src/ImageSharp/Memory/MemoryManager.cs
  22. 5
      src/ImageSharp/Memory/MemoryManagerExtensions.cs
  23. 56
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  24. 7
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs
  25. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
  26. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  27. 5
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  28. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  29. 5
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  30. 42
      tests/ImageSharp.Benchmarks/General/ClearBuffer.cs
  31. 362
      tests/ImageSharp.Benchmarks/General/PixelIndexing.cs
  32. 7
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  33. 251
      tests/ImageSharp.Tests/Memory/BufferTests.cs
  34. 248
      tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs
  35. 10
      tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs

4
src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs

@ -5,6 +5,7 @@ using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace SixLabors.ImageSharp.ColorSpaces
{
@ -143,7 +144,8 @@ namespace SixLabors.ImageSharp.ColorSpaces
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyChromaticityCoordinates other)
{
return this.backingVector.Equals(other.backingVector);
// The memberwise comparison here is a workaround for https://github.com/dotnet/coreclr/issues/16443
return this.X == other.X && this.Y == other.Y;
}
/// <inheritdoc/>

10
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -343,15 +343,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding;
}
using (var row = this.configuration.MemoryManager.Allocate<byte>(arrayWidth + padding, true))
using (IManagedByteBuffer row = this.configuration.MemoryManager.AllocateManagedByteBuffer(arrayWidth + padding, true))
{
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
Span<byte> rowSpan = row.Span;
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
this.currentStream.Read(row.Array, 0, row.Length);
this.currentStream.Read(row.Array, 0, row.Length());
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
@ -362,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int shift = 0; shift < ppb && (x + shift) < width; shift++)
{
int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int colorIndex = ((rowSpan[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int newX = colOffset + shift;
// Stored in b-> g-> r order.
@ -393,7 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255);
using (var buffer = this.configuration.MemoryManager.Allocate<byte>(stride))
using (var buffer = this.configuration.MemoryManager.AllocateManagedByteBuffer(stride))
{
for (int y = 0; y < height; y++)
{

21
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The global color table.
/// </summary>
private Buffer<byte> globalColorTable;
private IManagedByteBuffer globalColorTable;
/// <summary>
/// The global color table length
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
break;
}
this.globalColorTable = this.MemoryManager.Allocate<byte>(this.globalColorTableLength, true);
this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true);
nextFlag = stream.ReadByte();
if (nextFlag == -1)
@ -337,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue;
}
using (Buffer<byte> commentsBuffer = this.MemoryManager.Allocate<byte>(length))
using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length))
{
this.currentStream.Read(commentsBuffer.Array, 0, length);
string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length);
@ -357,22 +357,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
Buffer<byte> localColorTable = null;
Buffer<byte> indices = null;
IManagedByteBuffer localColorTable = null;
IManagedByteBuffer indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
if (imageDescriptor.LocalColorTableFlag)
{
int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryManager.Allocate<byte>(length, true);
localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true);
this.currentStream.Read(localColorTable.Array, 0, length);
}
indices = this.configuration.MemoryManager.Allocate<byte>(imageDescriptor.Width * imageDescriptor.Height, true);
indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
this.ReadFrameIndices(imageDescriptor, indices);
this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor);
this.ReadFrameIndices(imageDescriptor, indices.Span);
IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@ -605,7 +606,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = this.MemoryManager.Allocate<byte>(this.globalColorTableLength, true);
this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true);
// Read the global color table from the stream
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength);

10
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -356,15 +356,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
var rgb = default(Rgb24);
using (Buffer<byte> colorTable = this.memoryManager.Allocate<byte>(colorTableLength))
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
{
Span<byte> colorTableSpan = colorTable.Span;
for (int i = 0; i < pixelCount; i++)
{
int offset = i * 3;
image.Palette[i].ToRgb24(ref rgb);
colorTable[offset] = rgb.R;
colorTable[offset + 1] = rgb.G;
colorTable[offset + 2] = rgb.B;
colorTableSpan[offset] = rgb.R;
colorTableSpan[offset + 1] = rgb.G;
colorTableSpan[offset + 2] = rgb.B;
}
writer.Write(colorTable.Array, 0, colorTableLength);

19
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs

@ -26,8 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on.
/// TODO: Do we really need buffer here? Might be an optimiziation opportunity.
/// </summary>
public Buffer<byte> Buffer;
public IManagedByteBuffer Buffer;
/// <summary>
/// Values of <see cref="Buffer"/> converted to <see cref="int"/>-s
@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
return new Bytes
{
Buffer = memoryManager.Allocate<byte>(BufferSize),
Buffer = memoryManager.AllocateManagedByteBuffer(BufferSize),
BufferAsInt = memoryManager.Allocate<int>(BufferSize)
};
}
@ -169,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
}
}
result = this.Buffer[this.I];
result = this.Buffer.Span[this.I];
this.I++;
this.UnreadableBytes = 0;
return errorCode;
@ -229,18 +230,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist();
}
Span<byte> bufferSpan = this.Buffer.Span;
// Move the last 2 bytes to the start of the buffer, in case we need
// to call UnreadByteStuffedByte.
if (this.J > 2)
{
this.Buffer[0] = this.Buffer[this.J - 2];
this.Buffer[1] = this.Buffer[this.J - 1];
bufferSpan[0] = bufferSpan[this.J - 2];
bufferSpan[1] = bufferSpan[this.J - 1];
this.I = 2;
this.J = 2;
}
// Fill in the rest of the buffer.
int n = inputStream.Read(this.Buffer.Array, this.J, this.Buffer.Length - this.J);
int n = inputStream.Read(this.Buffer.Array, this.J, bufferSpan.Length - this.J);
if (n == 0)
{
return OrigDecoderErrorCode.UnexpectedEndOfStream;
@ -248,9 +251,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.J += n;
for (int i = 0; i < this.Buffer.Length; i++)
for (int i = 0; i < bufferSpan.Length; i++)
{
this.BufferAsInt[i] = this.Buffer[i];
this.BufferAsInt[i] = bufferSpan[i];
}
return OrigDecoderErrorCode.NoError;

21
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs

@ -12,10 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
internal struct PdfJsHuffmanTable : IDisposable
{
private Buffer<short> lookahead;
private Buffer<short> valOffset;
private Buffer<long> maxcode;
private Buffer<byte> huffval;
private FakeBuffer<short> lookahead;
private FakeBuffer<short> valOffset;
private FakeBuffer<long> maxcode;
private IManagedByteBuffer huffval;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct.
@ -25,12 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values)
{
this.lookahead = memoryManager.Allocate<short>(256, true);
this.valOffset = memoryManager.Allocate<short>(18, true);
this.maxcode = memoryManager.Allocate<long>(18, true);
// TODO: Replace FakeBuffer<T> usages with standard or array orfixed-sized arrays
this.lookahead = memoryManager.AllocateFake<short>(256);
this.valOffset = memoryManager.AllocateFake<short>(18);
this.maxcode = memoryManager.AllocateFake<long>(18);
using (var huffsize = memoryManager.Allocate<short>(257, true))
using (var huffcode = memoryManager.Allocate<short>(257, true))
using (FakeBuffer<short> huffsize = memoryManager.AllocateFake<short>(257))
using (FakeBuffer<short> huffcode = memoryManager.AllocateFake<short>(257))
{
GenerateSizeTable(lengths, huffsize);
GenerateCodeTable(huffsize, huffcode);
@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
GenerateLookaheadTables(lengths, values, this.lookahead);
}
this.huffval = memoryManager.Allocate<byte>(values.Length, true);
this.huffval = memoryManager.AllocateManagedByteBuffer(values.Length, true);
Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length);
this.MaxCode = this.maxcode.Array;

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs

@ -6,6 +6,8 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
using SixLabors.ImageSharp.Memory;
/// <summary>
/// Performs the inverse Descrete Cosine Transform on each frame component.
/// </summary>

14
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -673,23 +673,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException($"DHT has wrong length: {remaining}");
}
using (var huffmanData = this.configuration.MemoryManager.Allocate<byte>(256, true))
using (IManagedByteBuffer huffmanData = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256))
{
Span<byte> huffmanSpan = huffmanData.Span;
for (int i = 2; i < remaining;)
{
byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
this.InputStream.Read(huffmanData.Array, 0, 16);
using (var codeLengths = this.configuration.MemoryManager.Allocate<byte>(17, true))
using (IManagedByteBuffer codeLengths = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(17))
{
Span<byte> codeLengthsSpan = codeLengths.Span;
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
{
codeLengthSum += codeLengths[j] = huffmanData[j - 1];
codeLengthSum += codeLengthsSpan[j] = huffmanSpan[j - 1];
}
using (var huffmanValues = this.configuration.MemoryManager.Allocate<byte>(256, true))
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256))
{
this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum);
@ -784,8 +786,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
int blocksPerLine = component.BlocksPerLine;
int blocksPerColumn = component.BlocksPerColumn;
using (var computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
using (var multiplicationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
using (Buffer<short> computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
using (Buffer<short> multiplicationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
{
Span<short> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex);
Span<short> computationBufferSpan = computationBuffer;

2
src/ImageSharp/Formats/Png/PngChunk.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets or sets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length.
/// </summary>
public Buffer<byte> Data { get; set; }
public IManagedByteBuffer Data { get; set; }
/// <summary>
/// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,

71
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -137,12 +137,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Previous scanline processed
/// </summary>
private Buffer<byte> previousScanline;
private IManagedByteBuffer previousScanline;
/// <summary>
/// The current scanline that is being processed
/// </summary>
private Buffer<byte> scanline;
private IManagedByteBuffer scanline;
/// <summary>
/// The index of the current scanline being processed
@ -437,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = this.MemoryManager.Allocate<byte>(this.bytesPerScanline, true);
this.scanline = this.configuration.MemoryManager.Allocate<byte>(this.bytesPerScanline, true);
this.previousScanline = this.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.scanline = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
}
/// <summary>
@ -558,7 +558,8 @@ namespace SixLabors.ImageSharp.Formats.Png
}
this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0];
Span<byte> scanlineSpan = this.scanline.Span;
var filterType = (FilterType)scanlineSpan[0];
switch (filterType)
{
@ -567,22 +568,22 @@ namespace SixLabors.ImageSharp.Formats.Png
case FilterType.Sub:
SubFilter.Decode(this.scanline, this.bytesPerPixel);
SubFilter.Decode(scanlineSpan, this.bytesPerPixel);
break;
case FilterType.Up:
UpFilter.Decode(this.scanline, this.previousScanline);
UpFilter.Decode(scanlineSpan, this.previousScanline.Span);
break;
case FilterType.Average:
AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel);
AverageFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel);
break;
case FilterType.Paeth:
PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel);
PaethFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel);
break;
default:
@ -753,11 +754,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16)
{
int length = this.header.Width * 3;
using (var compressed = this.configuration.MemoryManager.Allocate<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed.Span, rowSpan, this.header.Width);
}
}
else
@ -770,10 +771,10 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16)
{
int length = this.header.Width * 3;
using (var compressed = this.configuration.MemoryManager.Allocate<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
Span<Rgb24> rgb24Span = compressed.Span.NonPortableCast<byte, Rgb24>();
for (int x = 0; x < this.header.Width; x++)
@ -811,11 +812,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16)
{
int length = this.header.Width * 4;
using (var compressed = this.configuration.MemoryManager.Allocate<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width);
this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed.Span, rowSpan, this.header.Width);
}
}
else
@ -1014,18 +1015,20 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16)
{
int length = this.header.Width * 3;
using (var compressed = this.configuration.MemoryManager.Allocate<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
Span<byte> compressedSpan = compressed.Span;
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length);
if (this.hasTrans)
{
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
rgba.R = compressedSpan[o];
rgba.G = compressedSpan[o + 1];
rgba.B = compressedSpan[o + 2];
rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255);
color.PackFromRgba32(rgba);
@ -1036,9 +1039,9 @@ namespace SixLabors.ImageSharp.Formats.Png
{
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
rgba.R = compressedSpan[o];
rgba.G = compressedSpan[o + 1];
rgba.B = compressedSpan[o + 2];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -1082,16 +1085,18 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16)
{
int length = this.header.Width * 4;
using (var compressed = this.configuration.MemoryManager.Allocate<byte>(length))
using (IBuffer<byte> compressed = this.configuration.MemoryManager.Allocate<byte>(length))
{
Span<byte> compressedSpan = compressed.Span;
// TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length);
this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length);
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4)
{
rgba.R = compressed[o];
rgba.G = compressed[o + 1];
rgba.B = compressed[o + 2];
rgba.A = compressed[o + 3];
rgba.R = compressedSpan[o];
rgba.G = compressedSpan[o + 1];
rgba.B = compressedSpan[o + 2];
rgba.A = compressedSpan[o + 3];
color.PackFromRgba32(rgba);
rowSpan[x] = color;
@ -1281,7 +1286,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ReadChunkData(PngChunk chunk)
{
// We rent the buffer here to return it afterwards in Decode()
chunk.Data = this.configuration.MemoryManager.Allocate<byte>(chunk.Length);
chunk.Data = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(chunk.Length);
this.currentStream.Read(chunk.Data.Array, 0, chunk.Length);
}
@ -1353,7 +1358,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void SwapBuffers()
{
Buffer<byte> temp = this.previousScanline;
IManagedByteBuffer temp = this.previousScanline;
this.previousScanline = this.scanline;
this.scanline = temp;
}

71
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -74,37 +74,37 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The previous scanline.
/// </summary>
private Buffer<byte> previousScanline;
private IManagedByteBuffer previousScanline;
/// <summary>
/// The raw scanline.
/// </summary>
private Buffer<byte> rawScanline;
private IManagedByteBuffer rawScanline;
/// <summary>
/// The filtered scanline result.
/// </summary>
private Buffer<byte> result;
private IManagedByteBuffer result;
/// <summary>
/// The buffer for the sub filter
/// </summary>
private Buffer<byte> sub;
private IManagedByteBuffer sub;
/// <summary>
/// The buffer for the up filter
/// </summary>
private Buffer<byte> up;
private IManagedByteBuffer up;
/// <summary>
/// The buffer for the average filter
/// </summary>
private Buffer<byte> average;
private IManagedByteBuffer average;
/// <summary>
/// The buffer for the paeth filter
/// </summary>
private Buffer<byte> paeth;
private IManagedByteBuffer paeth;
/// <summary>
/// The png color type.
@ -357,11 +357,11 @@ namespace SixLabors.ImageSharp.Formats.Png
{
if (this.bytesPerPixel == 4)
{
PixelOperations<TPixel>.Instance.ToRgba32Bytes(rowSpan, this.rawScanline, this.width);
PixelOperations<TPixel>.Instance.ToRgba32Bytes(rowSpan, this.rawScanline.Span, this.width);
}
else
{
PixelOperations<TPixel>.Instance.ToRgb24Bytes(rowSpan, this.rawScanline, this.width);
PixelOperations<TPixel>.Instance.ToRgb24Bytes(rowSpan, this.rawScanline.Span, this.width);
}
}
@ -373,13 +373,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="rowSpan">The row span.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private Buffer<byte> EncodePixelRow<TPixel>(Span<TPixel> rowSpan, int row)
private IManagedByteBuffer EncodePixelRow<TPixel>(Span<TPixel> rowSpan, int row)
where TPixel : struct, IPixel<TPixel>
{
switch (this.pngColorType)
{
case PngColorType.Palette:
Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length, this.rawScanline.Array, 0, this.rawScanline.Length);
// TODO: Use Span copy!
Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length(), this.rawScanline.Array, 0, this.rawScanline.Length());
break;
case PngColorType.Grayscale:
case PngColorType.GrayscaleWithAlpha:
@ -398,7 +399,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// to be most compressible, using lowest total variation as proxy for compressibility.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
private Buffer<byte> GetOptimalFilteredScanline()
private IManagedByteBuffer GetOptimalFilteredScanline()
{
Span<byte> scanSpan = this.rawScanline.Span;
Span<byte> prevSpan = this.previousScanline.Span;
@ -406,18 +407,18 @@ namespace SixLabors.ImageSharp.Formats.Png
// Palette images don't compress well with adaptive filtering.
if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8)
{
NoneFilter.Encode(this.rawScanline, this.result);
NoneFilter.Encode(this.rawScanline.Span, this.result.Span);
return this.result;
}
// This order, while different to the enumerated order is more likely to produce a smaller sum
// early on which shaves a couple of milliseconds off the processing time.
UpFilter.Encode(scanSpan, prevSpan, this.up, out int currentSum);
UpFilter.Encode(scanSpan, prevSpan, this.up.Span, out int currentSum);
int lowestSum = currentSum;
Buffer<byte> actualResult = this.up;
IManagedByteBuffer actualResult = this.up;
PaethFilter.Encode(scanSpan, prevSpan, this.paeth, this.bytesPerPixel, out currentSum);
PaethFilter.Encode(scanSpan, prevSpan, this.paeth.Span, this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
{
@ -425,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Png
actualResult = this.paeth;
}
SubFilter.Encode(scanSpan, this.sub, this.bytesPerPixel, out currentSum);
SubFilter.Encode(scanSpan, this.sub.Span, this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
{
@ -433,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Png
actualResult = this.sub;
}
AverageFilter.Encode(scanSpan, prevSpan, this.average, this.bytesPerPixel, out currentSum);
AverageFilter.Encode(scanSpan, prevSpan, this.average.Span, this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
{
@ -522,9 +523,13 @@ namespace SixLabors.ImageSharp.Formats.Png
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
var rgba = default(Rgba32);
bool anyAlpha = false;
using (Buffer<byte> colorTable = this.memoryManager.Allocate<byte>(colorTableLength))
using (Buffer<byte> alphaTable = this.memoryManager.Allocate<byte>(pixelCount))
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
using (IManagedByteBuffer alphaTable = this.memoryManager.AllocateManagedByteBuffer(pixelCount))
{
Span<byte> colorTableSpan = colorTable.Span;
Span<byte> alphaTableSpan = alphaTable.Span;
for (byte i = 0; i < pixelCount; i++)
{
if (quantized.Pixels.Contains(i))
@ -534,9 +539,9 @@ namespace SixLabors.ImageSharp.Formats.Png
byte alpha = rgba.A;
colorTable[offset] = rgba.R;
colorTable[offset + 1] = rgba.G;
colorTable[offset + 2] = rgba.B;
colorTableSpan[offset] = rgba.R;
colorTableSpan[offset + 1] = rgba.G;
colorTableSpan[offset + 2] = rgba.B;
if (alpha > this.threshold)
{
@ -544,7 +549,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
anyAlpha = anyAlpha || alpha < 255;
alphaTable[i] = alpha;
alphaTableSpan[i] = alpha;
}
}
@ -617,16 +622,16 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryManager.Allocate<byte>(this.bytesPerScanline, true);
this.rawScanline = this.memoryManager.Allocate<byte>(this.bytesPerScanline, true);
this.result = this.memoryManager.Allocate<byte>(resultLength, true);
this.previousScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.rawScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.result = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength);
if (this.pngColorType != PngColorType.Palette)
{
this.sub = this.memoryManager.Allocate<byte>(resultLength, true);
this.up = this.memoryManager.Allocate<byte>(resultLength, true);
this.average = this.memoryManager.Allocate<byte>(resultLength, true);
this.paeth = this.memoryManager.Allocate<byte>(resultLength, true);
this.sub = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength);
this.up = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength);
this.average = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength);
this.paeth = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength);
}
byte[] buffer;
@ -639,10 +644,10 @@ namespace SixLabors.ImageSharp.Formats.Png
{
for (int y = 0; y < this.height; y++)
{
Buffer<byte> r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y);
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y);
deflateStream.Write(r.Array, 0, resultLength);
Buffer<byte> temp = this.rawScanline;
IManagedByteBuffer temp = this.rawScanline;
this.rawScanline = this.previousScanline;
this.previousScanline = temp;
}

4
src/ImageSharp/Image/Image.Decode.cs

@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp
return null;
}
using (var buffer = config.MemoryManager.Allocate<byte>(maxHeaderSize))
using (IManagedByteBuffer buffer = config.MemoryManager.AllocateManagedByteBuffer(maxHeaderSize))
{
long startPosition = stream.Position;
stream.Read(buffer.Array, 0, maxHeaderSize);
stream.Position = startPosition;
return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null);
return config.FormatDetectors.Select(x => x.DetectFormat(buffer.Span)).LastOrDefault(x => x != null);
}
}

12
src/ImageSharp/Image/PixelAccessor{TPixel}.cs

@ -84,11 +84,6 @@ namespace SixLabors.ImageSharp
this.Dispose();
}
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TPixel[] PixelArray => this.PixelBuffer.Buffer.Array;
/// <summary>
/// Gets the size of a single pixel in the number of bytes.
/// </summary>
@ -106,7 +101,7 @@ namespace SixLabors.ImageSharp
public int Height { get; private set; }
/// <inheritdoc />
Span<TPixel> IBuffer2D<TPixel>.Span => this.PixelBuffer.Span;
public Span<TPixel> Span => this.PixelBuffer.Span;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
@ -122,14 +117,15 @@ namespace SixLabors.ImageSharp
get
{
this.CheckCoordinates(x, y);
return this.PixelArray[(y * this.Width) + x];
return this.Span[(y * this.Width) + x];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.CheckCoordinates(x, y);
this.PixelArray[(y * this.Width) + x] = value;
Span<TPixel> span = this.Span;
span[(y * this.Width) + x] = value;
}
}

41
src/ImageSharp/Image/PixelArea{TPixel}.cs

@ -30,44 +30,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// The underlying buffer containing the raw pixel data.
/// </summary>
private readonly Buffer<byte> byteBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="bytes">The bytes.</param>
/// <param name="componentOrder">The component order.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
/// </exception>
public PixelArea(int width, byte[] bytes, ComponentOrder componentOrder)
: this(width, 1, bytes, componentOrder)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="bytes">The bytes.</param>
/// <param name="componentOrder">The component order.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if <paramref name="bytes"></paramref> is the incorrect length.
/// </exception>
public PixelArea(int width, int height, byte[] bytes, ComponentOrder componentOrder)
{
this.CheckBytesLength(width, height, bytes, componentOrder);
this.Width = width;
this.Height = height;
this.ComponentOrder = componentOrder;
this.RowStride = width * GetComponentCount(componentOrder);
this.Length = bytes.Length; // TODO: Is this the right value for Length?
this.byteBuffer = new Buffer<byte>(bytes);
}
private readonly IManagedByteBuffer byteBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class.
@ -116,7 +79,7 @@ namespace SixLabors.ImageSharp
this.RowStride = (width * GetComponentCount(componentOrder)) + padding;
this.Length = this.RowStride * height;
this.byteBuffer = Configuration.Default.MemoryManager.Allocate<byte>(this.Length, true);
this.byteBuffer = Configuration.Default.MemoryManager.AllocateCleanManagedByteBuffer(this.Length);
}
/// <summary>

2
src/ImageSharp/Memory/ArrayPoolMemoryManager.cs

@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Memory
/// <inheritdoc />
internal override void Release<T>(Buffer<T> buffer)
{
byte[] byteBuffer = Unsafe.As<byte[]>(buffer.Array);
byte[] byteBuffer = Unsafe.As<byte[]>(buffer.GetArray());
this.pool.Return(byteBuffer);
}
}

1
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
internal static class Buffer2DExtensions
{
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>

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

@ -51,8 +51,8 @@ namespace SixLabors.ImageSharp.Memory
{
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return ref this.Buffer.Array[(this.Width * y) + x];
Span<T> span = this.Buffer.Span;
return ref span[(this.Width * y) + x];
}
}

54
src/ImageSharp/Memory/BufferExtensions.cs

@ -0,0 +1,54 @@
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
internal static class BufferExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length<T>(this IBuffer<T> buffer)
where T : struct => buffer.Span.Length;
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="start">The start</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start)
where T : struct
{
return buffer.Span.Slice(start);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="start">The start</param>
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start, int length)
where T : struct
{
return buffer.Span.Slice(start, length);
}
/// <summary>
/// Clears the contents of this buffer.
/// </summary>
/// <param name="buffer">The buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear<T>(this IBuffer<T> buffer)
where T : struct
{
buffer.Span.Clear();
}
public static ref T DangerousGetPinnableReference<T>(this IBuffer<T> buffer)
where T : struct =>
ref buffer.Span.DangerousGetPinnableReference();
}
}

87
src/ImageSharp/Memory/Buffer{T}.cs

@ -19,15 +19,23 @@ namespace SixLabors.ImageSharp.Memory
private MemoryManager memoryManager;
/// <summary>
/// A pointer to the first element of <see cref="Array"/> when pinned.
/// A pointer to the first element of <see cref="array"/> when pinned.
/// </summary>
private IntPtr pointer;
/// <summary>
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
/// A handle that allows to access the managed <see cref="array"/> as an unmanaged memory by pinning.
/// </summary>
private GCHandle handle;
// why is there such a rule? :S Protected should be fine for a field!
#pragma warning disable SA1401 // Fields should be private
/// <summary>
/// The backing array.
/// </summary>
protected T[] array;
#pragma warning restore SA1401 // Fields should be private
/// <summary>
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
@ -35,7 +43,7 @@ namespace SixLabors.ImageSharp.Memory
public Buffer(T[] array)
{
this.Length = array.Length;
this.Array = array;
this.array = array;
}
/// <summary>
@ -51,7 +59,7 @@ namespace SixLabors.ImageSharp.Memory
}
this.Length = length;
this.Array = array;
this.array = array;
}
internal Buffer(T[] array, int length, MemoryManager memoryManager)
@ -69,20 +77,15 @@ namespace SixLabors.ImageSharp.Memory
}
/// <summary>
/// Gets a value indicating whether this <see cref="Buffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// Gets a value indicating whether this <see cref="Buffer{T}"/> instance is disposed, or has lost ownership of <see cref="array"/>.
/// </summary>
public bool IsDisposedOrLostArrayOwnership { get; private set; }
/// <summary>
/// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when <see cref="Array"/> is pooled.
/// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when <see cref="array"/> is pooled.
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Gets the backing pinned array.
/// </summary>
public T[] Array { get; private set; }
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer.
/// </summary>
@ -112,7 +115,7 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer)
{
return new ReadOnlySpan<T>(buffer.Array, 0, buffer.Length);
return new ReadOnlySpan<T>(buffer.array, 0, buffer.Length);
}
/// <summary>
@ -122,30 +125,7 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Span<T>(Buffer<T> buffer)
{
return new Span<T>(buffer.Array, 0, buffer.Length);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="start">The start</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> Slice(int start)
{
return new Span<T>(this.Array, start, this.Length - start);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to an offsetted position inside the buffer.
/// </summary>
/// <param name="start">The start</param>
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> Slice(int start, int length)
{
return new Span<T>(this.Array, start, length);
return new Span<T>(buffer.array, 0, buffer.Length);
}
/// <summary>
@ -165,17 +145,17 @@ namespace SixLabors.ImageSharp.Memory
this.memoryManager?.Release(this);
this.memoryManager = null;
this.Array = null;
this.array = null;
this.Length = 0;
GC.SuppressFinalize(this);
}
/// <summary>
/// Unpins <see cref="Array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object.
/// If <see cref="Array"/> is rented, it's the callers responsibility to return it to it's pool.
/// Unpins <see cref="array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object.
/// If <see cref="array"/> is rented, it's the callers responsibility to return it to it's pool.
/// </summary>
/// <returns>The unpinned <see cref="Array"/></returns>
/// <returns>The unpinned <see cref="array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] TakeArrayOwnership()
{
@ -187,23 +167,14 @@ namespace SixLabors.ImageSharp.Memory
this.IsDisposedOrLostArrayOwnership = true;
this.UnPin();
T[] array = this.Array;
this.Array = null;
T[] array = this.array;
this.array = null;
this.memoryManager = null;
return array;
}
/// <summary>
/// Clears the contents of this buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.Span.Clear();
}
/// <summary>
/// Pins <see cref="Array"/>.
/// Pins <see cref="array"/>.
/// </summary>
/// <returns>The pinned pointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -217,7 +188,7 @@ namespace SixLabors.ImageSharp.Memory
if (this.pointer == IntPtr.Zero)
{
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned);
this.handle = GCHandle.Alloc(this.array, GCHandleType.Pinned);
this.pointer = this.handle.AddrOfPinnedObject();
}
@ -225,7 +196,15 @@ namespace SixLabors.ImageSharp.Memory
}
/// <summary>
/// Unpins <see cref="Array"/>.
/// TODO: Refactor this
/// </summary>
internal T[] GetArray()
{
return this.array;
}
/// <summary>
/// Unpins <see cref="array"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnPin()

2
src/ImageSharp/Memory/ManagedByteBuffer.cs

@ -6,5 +6,7 @@ namespace SixLabors.ImageSharp.Memory
: base(array, length, memoryManager)
{
}
public byte[] Array => this.array;
}
}

2
src/ImageSharp/Memory/MemoryManager.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Memory
/// Temporal workaround. A method providing a "Buffer" based on a generic array without the 'Unsafe.As()' hackery.
/// Should be replaced with 'Allocate()' as soon as SixLabors.Shapes has Span-based API-s!
/// </summary>
internal FakeBuffer<T> AllocateFake<T>(int length)
internal FakeBuffer<T> AllocateFake<T>(int length, bool dummy = false)
where T : struct
{
return new FakeBuffer<T>(new T[length]);

5
src/ImageSharp/Memory/MemoryManagerExtensions.cs

@ -26,6 +26,11 @@
return memoryManager.Allocate<T>(length, true);
}
public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryManager memoryManager, int length)
{
return memoryManager.AllocateManagedByteBuffer(length, false);
}
public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryManager memoryManager, int length)
{
return memoryManager.AllocateManagedByteBuffer(length, true);

56
src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

@ -180,14 +180,14 @@ namespace SixLabors.ImageSharp.Quantizers
{
this.Mark(ref this.colorCube[k], (byte)k);
float weight = Volume(ref this.colorCube[k], this.vwt.Array);
float weight = Volume(ref this.colorCube[k], this.vwt.Span);
if (MathF.Abs(weight) > Constants.Epsilon)
{
float r = Volume(ref this.colorCube[k], this.vmr.Array);
float g = Volume(ref this.colorCube[k], this.vmg.Array);
float b = Volume(ref this.colorCube[k], this.vmb.Array);
float a = Volume(ref this.colorCube[k], this.vma.Array);
float r = Volume(ref this.colorCube[k], this.vmr.Span);
float g = Volume(ref this.colorCube[k], this.vmg.Span);
float b = Volume(ref this.colorCube[k], this.vmb.Span);
float a = Volume(ref this.colorCube[k], this.vma.Span);
ref TPixel color = ref this.palette[k];
color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F);
@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.Quantizers
/// <param name="cube">The cube.</param>
/// <param name="moment">The moment.</param>
/// <returns>The result.</returns>
private static float Volume(ref Box cube, long[] moment)
private static float Volume(ref Box cube, Span<long> moment)
{
return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Quantizers
/// <param name="direction">The direction.</param>
/// <param name="moment">The moment.</param>
/// <returns>The result.</returns>
private static long Bottom(ref Box cube, int direction, long[] moment)
private static long Bottom(ref Box cube, int direction, Span<long> moment)
{
switch (direction)
{
@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp.Quantizers
/// <param name="position">The position.</param>
/// <param name="moment">The moment.</param>
/// <returns>The result.</returns>
private static long Top(ref Box cube, int direction, int position, long[] moment)
private static long Top(ref Box cube, int direction, int position, Span<long> moment)
{
switch (direction)
{
@ -548,10 +548,10 @@ namespace SixLabors.ImageSharp.Quantizers
/// <returns>The <see cref="float"/>.</returns>
private float Variance(ref Box cube)
{
float dr = Volume(ref cube, this.vmr.Array);
float dg = Volume(ref cube, this.vmg.Array);
float db = Volume(ref cube, this.vmb.Array);
float da = Volume(ref cube, this.vma.Array);
float dr = Volume(ref cube, this.vmr.Span);
float dg = Volume(ref cube, this.vmg.Span);
float db = Volume(ref cube, this.vmb.Span);
float da = Volume(ref cube, this.vma.Span);
float xx =
this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
@ -572,7 +572,7 @@ namespace SixLabors.ImageSharp.Quantizers
+ this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
var vector = new Vector4(dr, dg, db, da);
return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.Array));
return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.Span));
}
/// <summary>
@ -595,22 +595,22 @@ namespace SixLabors.ImageSharp.Quantizers
/// <returns>The <see cref="float"/>.</returns>
private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
{
long baseR = Bottom(ref cube, direction, this.vmr.Array);
long baseG = Bottom(ref cube, direction, this.vmg.Array);
long baseB = Bottom(ref cube, direction, this.vmb.Array);
long baseA = Bottom(ref cube, direction, this.vma.Array);
long baseW = Bottom(ref cube, direction, this.vwt.Array);
long baseR = Bottom(ref cube, direction, this.vmr.Span);
long baseG = Bottom(ref cube, direction, this.vmg.Span);
long baseB = Bottom(ref cube, direction, this.vmb.Span);
long baseA = Bottom(ref cube, direction, this.vma.Span);
long baseW = Bottom(ref cube, direction, this.vwt.Span);
float max = 0F;
cut = -1;
for (int i = first; i < last; i++)
{
float halfR = baseR + Top(ref cube, direction, i, this.vmr.Array);
float halfG = baseG + Top(ref cube, direction, i, this.vmg.Array);
float halfB = baseB + Top(ref cube, direction, i, this.vmb.Array);
float halfA = baseA + Top(ref cube, direction, i, this.vma.Array);
float halfW = baseW + Top(ref cube, direction, i, this.vwt.Array);
float halfR = baseR + Top(ref cube, direction, i, this.vmr.Span);
float halfG = baseG + Top(ref cube, direction, i, this.vmg.Span);
float halfB = baseB + Top(ref cube, direction, i, this.vmb.Span);
float halfA = baseA + Top(ref cube, direction, i, this.vma.Span);
float halfW = baseW + Top(ref cube, direction, i, this.vwt.Span);
if (MathF.Abs(halfW) < Constants.Epsilon)
{
@ -654,11 +654,11 @@ namespace SixLabors.ImageSharp.Quantizers
/// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut(ref Box set1, ref Box set2)
{
float wholeR = Volume(ref set1, this.vmr.Array);
float wholeG = Volume(ref set1, this.vmg.Array);
float wholeB = Volume(ref set1, this.vmb.Array);
float wholeA = Volume(ref set1, this.vma.Array);
float wholeW = Volume(ref set1, this.vwt.Array);
float wholeR = Volume(ref set1, this.vmr.Span);
float wholeG = Volume(ref set1, this.vmg.Span);
float wholeB = Volume(ref set1, this.vmb.Span);
float wholeA = Volume(ref set1, this.vma.Span);
float wholeW = Volume(ref set1, this.vwt.Span);
float maxr = this.Maximize(ref set1, 3, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
float maxg = this.Maximize(ref set1, 2, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW);

7
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs

@ -2,6 +2,7 @@
namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
@ -36,12 +37,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)]
public void PerElement()
{
Vector4[] s = this.source.Array;
TPixel[] d = this.destination.Array;
ref Vector4 s = ref this.source.Span.DangerousGetPinnableReference();
ref TPixel d = ref this.destination.Span.DangerousGetPinnableReference();
for (int i = 0; i < this.Count; i++)
{
d[i].PackFromVector4(s[i]);
Unsafe.Add(ref d, i).PackFromVector4(Unsafe.Add(ref s, i));
}
}

4
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs

@ -61,8 +61,8 @@
[Benchmark]
public void PackUsingReferences()
{
ref Vector4 sp = ref this.source.Array[0];
ref Rgba32 dp = ref this.destination.Array[0];
ref Vector4 sp = ref this.source.DangerousGetPinnableReference();
ref Rgba32 dp = ref this.destination.DangerousGetPinnableReference();
int count = this.Count;
for (int i = 0; i < count; i++)

8
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs

@ -1,6 +1,8 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
@ -33,13 +35,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)]
public void PerElement()
{
byte[] s = this.source.Array;
TPixel[] d = this.destination.Array;
Span<byte> s = this.source.Span;
Span<TPixel> d = this.destination.Span;
for (int i = 0; i < this.Count; i++)
{
int i4 = i * 4;
TPixel c = default(TPixel);
var c = default(TPixel);
c.PackFromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3]));
d[i] = c;
}

5
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs

@ -1,6 +1,7 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
@ -35,8 +36,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)]
public void PerElement()
{
TPixel[] s = this.source.Array;
Vector4[] d = this.destination.Array;
Span<TPixel> s = this.source.Span;
Span<Vector4> d = this.destination.Span;
for (int i = 0; i < this.Count; i++)
{

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs

@ -1,6 +1,9 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
@ -33,8 +36,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)]
public void PerElement()
{
TPixel[] s = this.source.Array;
byte[] d = this.destination.Array;
Span<TPixel> s = this.source.Span;
Span<byte> d = this.destination.Span;
var rgb = default(Rgb24);
for (int i = 0; i < this.Count; i++)

5
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs

@ -38,8 +38,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)]
public void PerElement()
{
TPixel[] s = this.source.Array;
byte[] d = this.destination.Array;
Span<TPixel> s = this.source.Span;
Span<byte> d = this.destination.Span;
var rgba = default(Rgba32);
for (int i = 0; i < this.Count; i++)

42
tests/ImageSharp.Benchmarks/General/ClearBuffer.cs

@ -1,42 +0,0 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.General
{
using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
public unsafe class ClearBuffer
{
private Buffer<Rgba32> buffer;
[Params(32, 128, 512)]
public int Count { get; set; }
[GlobalSetup]
public void Setup()
{
this.buffer = Configuration.Default.MemoryManager.Allocate<Rgba32>(this.Count);
}
[GlobalCleanup]
public void Cleanup()
{
this.buffer.Dispose();
}
[Benchmark(Baseline = true)]
public void Array_Clear()
{
Array.Clear(this.buffer.Array, 0, this.Count);
}
[Benchmark]
public void Unsafe_InitBlock()
{
Unsafe.InitBlock((void*)this.buffer.Pin(), default(byte), (uint)this.Count * sizeof(uint));
}
}
}

362
tests/ImageSharp.Benchmarks/General/PixelIndexing.cs

@ -1,362 +0,0 @@
namespace SixLabors.ImageSharp.Benchmarks.General
{
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
// Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row.
public abstract unsafe class PixelIndexing
{
/// <summary>
/// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs
/// </summary>
protected class Pinnable<T>
{
public T Data;
}
/// <summary>
/// The indexer methods are encapsulated into a struct to make sure everything is inlined.
/// </summary>
internal struct Data
{
private Vector4* pointer;
private Pinnable<Vector4> pinnable;
private Vector4[] array;
private int width;
public Data(Buffer2D<Vector4> buffer)
{
this.pointer = (Vector4*)buffer.Buffer.Pin();
this.pinnable = Unsafe.As<Pinnable<Vector4>>(buffer.Buffer.Array);
this.array = buffer.Buffer.Array;
this.width = buffer.Width;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetPointersBasicImpl(int x, int y)
{
return this.pointer[y * this.width + x];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetPointersSrcsUnsafeImpl(int x, int y)
{
// This is the original solution in PixelAccessor:
return Unsafe.Read<Vector4>((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf<Vector4>()));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetReferencesImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 GetReferencesRefReturnsImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return ref Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithPointersBasicImpl(int x, int y, Vector4 v)
{
this.pointer[y * this.width + x] = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v)
{
Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf<Vector4>()), v);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v)
{
int elementOffset = (y * this.width) + x;
// Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68
Unsafe.Add(ref this.pinnable.Data, elementOffset) = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
// Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68
return ref Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithUnsafeReferenceArithmeticsOnArray0Impl(int x, int y, Vector4 v)
{
int elementOffset = (y * this.width) + x;
Unsafe.Add(ref this.array[0], elementOffset) = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return ref Unsafe.Add(ref this.array[0], elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexSetArrayStraightforward(int x, int y, Vector4 v)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
this.array[(y * this.width) + x] = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithReferencesOnArrayStraightforwardRefReturnImpl(int x, int y)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
return ref this.array[(y * this.width) + x];
}
}
internal Buffer2D<Vector4> buffer;
protected int width;
protected int startIndex;
protected int endIndex;
protected Vector4* pointer;
protected Vector4[] array;
protected Pinnable<Vector4> pinnable;
// [Params(1024)]
public int Count { get; set; } = 1024;
[GlobalSetup]
public void Setup()
{
this.width = 2048;
this.buffer = Configuration.Default.MemoryManager.Allocate2D<Vector4>(2048, 2048);
this.pointer = (Vector4*)this.buffer.Buffer.Pin();
this.array = this.buffer.Buffer.Array;
this.pinnable = Unsafe.As<Pinnable<Vector4>>(this.array);
this.startIndex = 2048 / 2 - (this.Count / 2);
this.endIndex = 2048 / 2 + (this.Count / 2);
}
[GlobalCleanup]
public void Cleanup()
{
this.buffer.Dispose();
}
}
public class PixelIndexingGetter : PixelIndexing
{
[Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)]
public Vector4 IndexWithPointersBasic()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetPointersBasicImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")]
public Vector4 IndexWithPointersSrcsUnsafe()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetPointersSrcsUnsafeImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: References")]
public Vector4 IndexWithReferences()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetReferencesImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: References|refreturns")]
public Vector4 IndexWithReferencesRefReturns()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetReferencesRefReturnsImpl(x, y);
}
return sum;
}
}
public class PixelIndexingSetter : PixelIndexing
{
[Benchmark(Description = "!!! Index.Set: Pointers|arithmetics", Baseline = true)]
public void IndexWithPointersBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithPointersBasicImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: Pointers|SRCS.Unsafe")]
public void IndexWithPointersSrcsUnsafe()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithPointersSrcsUnsafeImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|IncorrectPinnable")]
public void IndexWithReferencesPinnableBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")]
public void IndexWithReferencesPinnableRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "Index.Set: References|Array[0]Unsafe")]
public void IndexWithReferencesArrayBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithUnsafeReferenceArithmeticsOnArray0Impl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|Array[0]Unsafe|refreturn")]
public void IndexWithReferencesArrayRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "!!! Index.Set: References|Array+Straight")]
public void IndexWithReferencesArrayStraightforward()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
data.IndexSetArrayStraightforward(x, y, v);
}
}
[Benchmark(Description = "!!! Index.Set: References|Array+Straight|refreturn")]
public void IndexWithReferencesArrayStraightforwardRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "!!! Index.Set: SmartUnsafe")]
public void SmartUnsafe()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
// This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row.
// If a user seriously needs by-pixel manipulation to be performant, we should provide this option.
ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(this.startIndex, this.startIndex);
for (int i = 0; i < this.Count; i++)
{
// We don't have to add 'Width * y' here!
Unsafe.Add(ref rowStart, i) = v;
}
}
}
}

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

@ -46,10 +46,11 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
using (Buffer2D<int> buffer = Configuration.Default.MemoryManager.Allocate2D<int>(42, 42, true))
{
Span<int> span = buffer.Span;
for (int j = 0; j < buffer.Buffer.Length; j++)
{
Assert.Equal(0, buffer.Buffer.Array[j]);
buffer.Buffer.Array[j] = 666;
Assert.Equal(0, span[j]);
span[j] = 666;
}
}
}
@ -95,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
using (Buffer2D<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate2D<TestStructs.Foo>(width, height))
{
TestStructs.Foo[] array = buffer.Buffer.Array;
Span<TestStructs.Foo> array = buffer.Buffer.Span;
ref TestStructs.Foo actual = ref buffer[x, y];

251
tests/ImageSharp.Tests/Memory/BufferTests.cs

@ -1,251 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.Memory
{
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
public unsafe class BufferTests
{
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(Span<T> span, Buffer<T> buffer, int bufferOffset = 0)
where T : struct
{
ref T actual = ref span.DangerousGetPinnableReference();
ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset);
Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position");
}
public static void Equal(void* expected, void* actual)
{
Assert.Equal((IntPtr)expected, (IntPtr)actual);
}
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
public void ConstructWithOwnArray(int count)
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(count))
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.NotNull(buffer.Array);
Assert.Equal(count, buffer.Length);
Assert.True(buffer.Array.Length >= count);
}
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
public void ConstructWithExistingArray(int count)
{
TestStructs.Foo[] array = new TestStructs.Foo[count];
using (Buffer<TestStructs.Foo> buffer = new Buffer<TestStructs.Foo>(array))
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.Equal(array, buffer.Array);
Assert.Equal(count, buffer.Length);
}
}
[Fact]
public void Clear()
{
TestStructs.Foo[] a = { new TestStructs.Foo() { A = 1, B = 2 }, new TestStructs.Foo() { A = 3, B = 4 } };
using (Buffer<TestStructs.Foo> buffer = new Buffer<TestStructs.Foo>(a))
{
buffer.Clear();
Assert.Equal(default(TestStructs.Foo), a[0]);
Assert.Equal(default(TestStructs.Foo), a[1]);
}
}
[Fact]
public void CreateClean()
{
for (int i = 0; i < 100; i++)
{
using (Buffer<int> buffer = Configuration.Default.MemoryManager.Allocate<int>(42, true))
{
for (int j = 0; j < buffer.Length; j++)
{
Assert.Equal(0, buffer.Array[j]);
buffer.Array[j] = 666;
}
}
}
}
public class Indexer
{
public static readonly TheoryData<int, int> IndexerData =
new TheoryData<int,int>()
{
{ 10, 0 },
{ 16, 3 },
{ 10, 9 }
};
[Theory]
[MemberData(nameof(IndexerData))]
public void Read(int length, int index)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
using (Buffer<TestStructs.Foo> buffer = new Buffer<TestStructs.Foo>(a))
{
TestStructs.Foo element = buffer[index];
Assert.Equal(a[index], element);
}
}
[Theory]
[MemberData(nameof(IndexerData))]
public void Write(int length, int index)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
using (Buffer<TestStructs.Foo> buffer = new Buffer<TestStructs.Foo>(a))
{
buffer[index] = new TestStructs.Foo(666, 666);
Assert.Equal(new TestStructs.Foo(666, 666), a[index]);
}
}
}
[Fact]
public void Dispose()
{
Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42);
buffer.Dispose();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
[Theory]
[InlineData(7)]
[InlineData(123)]
public void CastToSpan(int bufferLength)
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(bufferLength))
{
Span<TestStructs.Foo> span = buffer;
//Assert.Equal(buffer.Array, span.ToArray());
//Assert.Equal(0, span.Start);
Assert.SpanPointsTo(span, buffer);
Assert.Equal(span.Length, bufferLength);
}
}
[Fact]
public void Span()
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42))
{
Span<TestStructs.Foo> span = buffer.Span;
// Assert.Equal(buffer.Array, span.ToArray());
// Assert.Equal(0, span.Start);
Assert.SpanPointsTo(span, buffer);
Assert.Equal(42, span.Length);
}
}
public class Slice
{
[Theory]
[InlineData(7, 2)]
[InlineData(123, 17)]
public void WithStartOnly(int bufferLength, int start)
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(bufferLength))
{
Span<TestStructs.Foo> span = buffer.Slice(start);
Assert.SpanPointsTo(span, buffer, start);
Assert.Equal(span.Length, bufferLength - start);
}
}
[Theory]
[InlineData(7, 2, 5)]
[InlineData(123, 17, 42)]
public void WithStartAndLength(int bufferLength, int start, int spanLength)
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(bufferLength))
{
Span<TestStructs.Foo> span = buffer.Slice(start, spanLength);
Assert.SpanPointsTo(span, buffer, start);
Assert.Equal(span.Length, spanLength);
}
}
}
[Fact]
public void UnPinAndTakeArrayOwnership()
{
TestStructs.Foo[] data = null;
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42))
{
data = buffer.TakeArrayOwnership();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
Assert.NotNull(data);
Assert.True(data.Length >= 42);
}
public class Pin
{
[Fact]
public void ReturnsPinnedPointerToTheBeginningOfArray()
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42))
{
TestStructs.Foo* actual = (TestStructs.Foo*)buffer.Pin();
fixed (TestStructs.Foo* expected = buffer.Array)
{
Assert.Equal(expected, actual);
}
}
}
[Fact]
public void SecondCallReturnsTheSamePointer()
{
using (Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42))
{
IntPtr ptr1 = buffer.Pin();
IntPtr ptr2 = buffer.Pin();
Assert.Equal(ptr1, ptr2);
}
}
[Fact]
public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException()
{
Buffer<TestStructs.Foo> buffer = Configuration.Default.MemoryManager.Allocate<TestStructs.Foo>(42);
buffer.Dispose();
Assert.Throws<InvalidOperationException>(() => buffer.Pin());
}
}
}
}

248
tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace SixLabors.ImageSharp.Tests.Memory
{
using System;
@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
float[] stuff = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
Span<float> span = new Span<float>(stuff);
var span = new Span<float>(stuff);
ref Vector<float> v = ref span.FetchVector();
@ -39,199 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(2, v[2]);
Assert.Equal(3, v[3]);
}
[Fact]
public void AsBytes()
{
TestStructs.Foo[] fooz = { new TestStructs.Foo(1, 2), new TestStructs.Foo(3, 4), new TestStructs.Foo(5, 6) };
using (Buffer<TestStructs.Foo> colorBuf = new Buffer<TestStructs.Foo>(fooz))
{
Span<TestStructs.Foo> orig = colorBuf.Slice(1);
Span<byte> asBytes = orig.AsBytes();
// Assert.Equal(asBytes.Start, sizeof(Foo));
Assert.Equal(orig.Length * Unsafe.SizeOf<TestStructs.Foo>(), asBytes.Length);
Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference());
}
}
public class Construct
{
[Fact]
public void Basic()
{
TestStructs.Foo[] array = TestStructs.Foo.CreateArray(3);
// Act:
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(array);
// Assert:
Assert.Equal(array, span.ToArray());
Assert.Equal(3, span.Length);
Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference());
}
[Fact]
public void WithStart()
{
TestStructs.Foo[] array = TestStructs.Foo.CreateArray(4);
int start = 2;
// Act:
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(array, start);
// Assert:
Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal(array.Length - start, span.Length);
}
[Fact]
public void WithStartAndLength()
{
TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10);
int start = 2;
int length = 3;
// Act:
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(array, start, length);
// Assert:
Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal(length, span.Length);
}
}
public class Slice
{
[Fact]
public void StartOnly()
{
TestStructs.Foo[] array = TestStructs.Foo.CreateArray(5);
int start0 = 2;
int start1 = 2;
int totalOffset = start0 + start1;
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(array, start0);
// Act:
span = span.Slice(start1);
// Assert:
Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(array.Length - totalOffset, span.Length);
}
[Fact]
public void StartAndLength()
{
TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10);
int start0 = 2;
int start1 = 2;
int totalOffset = start0 + start1;
int sliceLength = 3;
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(array, start0);
// Act:
span = span.Slice(start1, sliceLength);
// Assert:
Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(sliceLength, span.Length);
}
}
//[Theory]
//[InlineData(4)]
//[InlineData(1500)]
//public void Clear(int count)
//{
// Foo[] array = Foo.CreateArray(count + 42);
// int offset = 2;
// Span<Foo> ap = new Span<Foo>(array, offset);
// // Act:
// ap.Clear(count);
// Assert.NotEqual(default(Foo), array[offset - 1]);
// Assert.Equal(default(Foo), array[offset]);
// Assert.Equal(default(Foo), array[offset + count - 1]);
// Assert.NotEqual(default(Foo), array[offset + count]);
//}
public class Indexer
{
public static readonly TheoryData<int, int, int> IndexerData =
new TheoryData<int, int, int>()
{
{ 10, 0, 0 },
{ 10, 2, 0 },
{ 16, 0, 3 },
{ 16, 2, 3 },
{ 10, 0, 9 },
{ 10, 1, 8 }
};
[Theory]
[MemberData(nameof(IndexerData))]
public void Read(int length, int start, int index)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(a, start);
TestStructs.Foo element = span[index];
Assert.Equal(a[start + index], element);
}
[Theory]
[MemberData(nameof(IndexerData))]
public void Write(int length, int start, int index)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(a, start);
span[index] = new TestStructs.Foo(666, 666);
Assert.Equal(new TestStructs.Foo(666, 666), a[start + index]);
}
[Theory]
[InlineData(10, 0, 0, 5)]
[InlineData(10, 1, 1, 5)]
[InlineData(10, 1, 1, 6)]
[InlineData(10, 1, 1, 7)]
public void AsBytes_Read(int length, int start, int index, int byteOffset)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(a, start);
Span<byte> bytes = span.AsBytes();
byte actual = bytes[index * Unsafe.SizeOf<TestStructs.Foo>() + byteOffset];
ref byte baseRef = ref Unsafe.As<TestStructs.Foo, byte>(ref a[0]);
byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf<TestStructs.Foo>() + byteOffset);
Assert.Equal(expected, actual);
}
}
[Theory]
[InlineData(0, 4)]
[InlineData(2, 4)]
[InlineData(3, 4)]
public void DangerousGetPinnableReference(int start, int length)
{
TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length);
Span<TestStructs.Foo> span = new Span<TestStructs.Foo>(a, start);
ref TestStructs.Foo r = ref span.DangerousGetPinnableReference();
Assert.True(Unsafe.AreSame(ref a[start], ref r));
}
public class Copy
public class SpanHelper_Copy
{
private static void AssertNotDefault<T>(T[] data, int idx)
where T : struct
@ -267,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2);
TestStructs.Foo[] dest = new TestStructs.Foo[count + 5];
Span<TestStructs.Foo> apSource = new Span<TestStructs.Foo>(source, 1);
Span<TestStructs.Foo> apDest = new Span<TestStructs.Foo>(dest, 1);
var apSource = new Span<TestStructs.Foo>(source, 1);
var apDest = new Span<TestStructs.Foo>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 1);
@ -290,8 +101,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2);
TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5];
Span<TestStructs.AlignedFoo> apSource = new Span<TestStructs.AlignedFoo>(source, 1);
Span<TestStructs.AlignedFoo> apDest = new Span<TestStructs.AlignedFoo>(dest, 1);
var apSource = new Span<TestStructs.AlignedFoo>(source, 1);
var apDest = new Span<TestStructs.AlignedFoo>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 1);
@ -313,8 +124,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
int[] source = CreateTestInts(count + 2);
int[] dest = new int[count + 5];
Span<int> apSource = new Span<int>(source, 1);
Span<int> apDest = new Span<int>(dest, 1);
var apSource = new Span<int>(source, 1);
var apDest = new Span<int>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 1);
@ -337,8 +148,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2];
Span<TestStructs.Foo> apSource = new Span<TestStructs.Foo>(source, 1);
Span<byte> apDest = new Span<byte>(dest, sizeof(TestStructs.Foo));
var apSource = new Span<TestStructs.Foo>(source, 1);
var apDest = new Span<byte>(dest, sizeof(TestStructs.Foo));
SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.Foo));
@ -360,8 +171,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2];
Span<TestStructs.AlignedFoo> apSource = new Span<TestStructs.AlignedFoo>(source, 1);
Span<byte> apDest = new Span<byte>(dest, sizeof(TestStructs.AlignedFoo));
var apSource = new Span<TestStructs.AlignedFoo>(source, 1);
var apDest = new Span<byte>(dest, sizeof(TestStructs.AlignedFoo));
SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.AlignedFoo));
@ -383,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
int[] source = CreateTestInts(count + 2);
byte[] dest = new byte[destCount + sizeof(int) + 1];
Span<int> apSource = new Span<int>(source);
Span<byte> apDest = new Span<byte>(dest);
var apSource = new Span<int>(source);
var apDest = new Span<byte>(dest);
SpanHelper.Copy(apSource.AsBytes(), apDest, count * sizeof(int));
@ -404,8 +215,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
byte[] source = CreateTestBytes(srcCount);
TestStructs.Foo[] dest = new TestStructs.Foo[count + 2];
Span<byte> apSource = new Span<byte>(source);
Span<TestStructs.Foo> apDest = new Span<TestStructs.Foo>(dest);
var apSource = new Span<byte>(source);
var apDest = new Span<TestStructs.Foo>(dest);
SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(TestStructs.Foo));
@ -417,26 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.True((bool)ElementsAreEqual(dest, source, count - 1));
Assert.False((bool)ElementsAreEqual(dest, source, count));
}
[Fact]
public void Color32ToBytes()
{
Rgba32[] colors = { new Rgba32(0, 1, 2, 3), new Rgba32(4, 5, 6, 7), new Rgba32(8, 9, 10, 11), };
using (Buffer<Rgba32> colorBuf = new Buffer<Rgba32>(colors))
using (Buffer<byte> byteBuf = Configuration.Default.MemoryManager.Allocate<byte>(colors.Length * 4))
{
SpanHelper.Copy(colorBuf.Span.AsBytes(), byteBuf, colorBuf.Length * sizeof(Rgba32));
byte[] a = byteBuf.Array;
for (int i = 0; i < byteBuf.Length; i++)
{
Assert.Equal((byte)i, a[i]);
}
}
}
internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index)
{
fixed (TestStructs.Foo* pArray = array)

10
tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs

@ -375,8 +375,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
}
else
{
TDest[] expected = this.ExpectedDestBuffer.Array;
TDest[] actual = this.ActualDestBuffer.Array;
Span<TDest> expected = this.ExpectedDestBuffer.Span;
Span<TDest> actual = this.ActualDestBuffer.Span;
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], actual[i]);
@ -402,7 +402,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
internal static Vector4[] CreateVector4TestData(int length)
{
Vector4[] result = new Vector4[length];
Random rnd = new Random(42); // Deterministic random values
var rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{
@ -415,7 +415,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
{
TPixel[] result = new TPixel[length];
Random rnd = new Random(42); // Deterministic random values
var rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{
@ -429,7 +429,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
internal static byte[] CreateByteTestData(int length)
{
byte[] result = new byte[length];
Random rnd = new Random(42); // Deterministic random values
var rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{

Loading…
Cancel
Save