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.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace SixLabors.ImageSharp.ColorSpaces namespace SixLabors.ImageSharp.ColorSpaces
{ {
@ -143,7 +144,8 @@ namespace SixLabors.ImageSharp.ColorSpaces
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyChromaticityCoordinates other) 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/> /// <inheritdoc/>

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

@ -343,15 +343,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding; 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 color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);
Span<byte> rowSpan = row.Span;
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
int newY = Invert(y, height, inverted); 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; int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); 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++) 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; int newX = colOffset + shift;
// Stored in b-> g-> r order. // Stored in b-> g-> r order.
@ -393,7 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
var color = default(TPixel); var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255); 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++) for (int y = 0; y < height; y++)
{ {

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

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The global color table. /// The global color table.
/// </summary> /// </summary>
private Buffer<byte> globalColorTable; private IManagedByteBuffer globalColorTable;
/// <summary> /// <summary>
/// The global color table length /// The global color table length
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
break; break;
} }
this.globalColorTable = this.MemoryManager.Allocate<byte>(this.globalColorTableLength, true); this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true);
nextFlag = stream.ReadByte(); nextFlag = stream.ReadByte();
if (nextFlag == -1) if (nextFlag == -1)
@ -337,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue; continue;
} }
using (Buffer<byte> commentsBuffer = this.MemoryManager.Allocate<byte>(length)) using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length))
{ {
this.currentStream.Read(commentsBuffer.Array, 0, length); this.currentStream.Read(commentsBuffer.Array, 0, length);
string comments = this.TextEncoding.GetString(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(); GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
Buffer<byte> localColorTable = null; IManagedByteBuffer localColorTable = null;
Buffer<byte> indices = null; IManagedByteBuffer indices = null;
try try
{ {
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
if (imageDescriptor.LocalColorTableFlag) if (imageDescriptor.LocalColorTableFlag)
{ {
int length = imageDescriptor.LocalColorTableSize * 3; 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); 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.ReadFrameIndices(imageDescriptor, indices.Span);
this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor); IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
this.Skip(0); this.Skip(0);
@ -605,7 +606,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; 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 // Read the global color table from the stream
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); 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. // Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
var rgb = default(Rgb24); 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++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; int offset = i * 3;
image.Palette[i].ToRgb24(ref rgb); image.Palette[i].ToRgb24(ref rgb);
colorTable[offset] = rgb.R; colorTableSpan[offset] = rgb.R;
colorTable[offset + 1] = rgb.G; colorTableSpan[offset + 1] = rgb.G;
colorTable[offset + 2] = rgb.B; colorTableSpan[offset + 2] = rgb.B;
} }
writer.Write(colorTable.Array, 0, colorTableLength); 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. /// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying /// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on. /// stream that haven't yet been passed further on.
/// TODO: Do we really need buffer here? Might be an optimiziation opportunity.
/// </summary> /// </summary>
public Buffer<byte> Buffer; public IManagedByteBuffer Buffer;
/// <summary> /// <summary>
/// Values of <see cref="Buffer"/> converted to <see cref="int"/>-s /// 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 return new Bytes
{ {
Buffer = memoryManager.Allocate<byte>(BufferSize), Buffer = memoryManager.AllocateManagedByteBuffer(BufferSize),
BufferAsInt = memoryManager.Allocate<int>(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.I++;
this.UnreadableBytes = 0; this.UnreadableBytes = 0;
return errorCode; return errorCode;
@ -229,18 +230,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist(); DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist();
} }
Span<byte> bufferSpan = this.Buffer.Span;
// Move the last 2 bytes to the start of the buffer, in case we need // Move the last 2 bytes to the start of the buffer, in case we need
// to call UnreadByteStuffedByte. // to call UnreadByteStuffedByte.
if (this.J > 2) if (this.J > 2)
{ {
this.Buffer[0] = this.Buffer[this.J - 2]; bufferSpan[0] = bufferSpan[this.J - 2];
this.Buffer[1] = this.Buffer[this.J - 1]; bufferSpan[1] = bufferSpan[this.J - 1];
this.I = 2; this.I = 2;
this.J = 2; this.J = 2;
} }
// Fill in the rest of the buffer. // 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) if (n == 0)
{ {
return OrigDecoderErrorCode.UnexpectedEndOfStream; return OrigDecoderErrorCode.UnexpectedEndOfStream;
@ -248,9 +251,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.J += n; 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; return OrigDecoderErrorCode.NoError;

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

@ -12,10 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
internal struct PdfJsHuffmanTable : IDisposable internal struct PdfJsHuffmanTable : IDisposable
{ {
private Buffer<short> lookahead; private FakeBuffer<short> lookahead;
private Buffer<short> valOffset; private FakeBuffer<short> valOffset;
private Buffer<long> maxcode; private FakeBuffer<long> maxcode;
private Buffer<byte> huffval; private IManagedByteBuffer huffval;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct. /// 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> /// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values) public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values)
{ {
this.lookahead = memoryManager.Allocate<short>(256, true); // TODO: Replace FakeBuffer<T> usages with standard or array orfixed-sized arrays
this.valOffset = memoryManager.Allocate<short>(18, true); this.lookahead = memoryManager.AllocateFake<short>(256);
this.maxcode = memoryManager.Allocate<long>(18, true); this.valOffset = memoryManager.AllocateFake<short>(18);
this.maxcode = memoryManager.AllocateFake<long>(18);
using (var huffsize = memoryManager.Allocate<short>(257, true)) using (FakeBuffer<short> huffsize = memoryManager.AllocateFake<short>(257))
using (var huffcode = memoryManager.Allocate<short>(257, true)) using (FakeBuffer<short> huffcode = memoryManager.AllocateFake<short>(257))
{ {
GenerateSizeTable(lengths, huffsize); GenerateSizeTable(lengths, huffsize);
GenerateCodeTable(huffsize, huffcode); GenerateCodeTable(huffsize, huffcode);
@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
GenerateLookaheadTables(lengths, values, this.lookahead); 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); Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length);
this.MaxCode = this.maxcode.Array; 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 namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
using SixLabors.ImageSharp.Memory;
/// <summary> /// <summary>
/// Performs the inverse Descrete Cosine Transform on each frame component. /// Performs the inverse Descrete Cosine Transform on each frame component.
/// </summary> /// </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}"); 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;) for (int i = 2; i < remaining;)
{ {
byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
this.InputStream.Read(huffmanData.Array, 0, 16); 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; int codeLengthSum = 0;
for (int j = 1; j < 17; j++) 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); this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum);
@ -784,8 +786,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{ {
int blocksPerLine = component.BlocksPerLine; int blocksPerLine = component.BlocksPerLine;
int blocksPerColumn = component.BlocksPerColumn; int blocksPerColumn = component.BlocksPerColumn;
using (var computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true)) using (Buffer<short> computationBuffer = this.configuration.MemoryManager.Allocate<short>(64, true))
using (var multiplicationBuffer = 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> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex);
Span<short> computationBufferSpan = computationBuffer; 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. /// Gets or sets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length. /// This field can be of zero length.
/// </summary> /// </summary>
public Buffer<byte> Data { get; set; } public IManagedByteBuffer Data { get; set; }
/// <summary> /// <summary>
/// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, /// 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> /// <summary>
/// Previous scanline processed /// Previous scanline processed
/// </summary> /// </summary>
private Buffer<byte> previousScanline; private IManagedByteBuffer previousScanline;
/// <summary> /// <summary>
/// The current scanline that is being processed /// The current scanline that is being processed
/// </summary> /// </summary>
private Buffer<byte> scanline; private IManagedByteBuffer scanline;
/// <summary> /// <summary>
/// The index of the current scanline being processed /// The index of the current scanline being processed
@ -437,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8; this.bytesPerSample = this.header.BitDepth / 8;
} }
this.previousScanline = this.MemoryManager.Allocate<byte>(this.bytesPerScanline, true); this.previousScanline = this.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.scanline = this.configuration.MemoryManager.Allocate<byte>(this.bytesPerScanline, true); this.scanline = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
} }
/// <summary> /// <summary>
@ -558,7 +558,8 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0]; Span<byte> scanlineSpan = this.scanline.Span;
var filterType = (FilterType)scanlineSpan[0];
switch (filterType) switch (filterType)
{ {
@ -567,22 +568,22 @@ namespace SixLabors.ImageSharp.Formats.Png
case FilterType.Sub: case FilterType.Sub:
SubFilter.Decode(this.scanline, this.bytesPerPixel); SubFilter.Decode(scanlineSpan, this.bytesPerPixel);
break; break;
case FilterType.Up: case FilterType.Up:
UpFilter.Decode(this.scanline, this.previousScanline); UpFilter.Decode(scanlineSpan, this.previousScanline.Span);
break; break;
case FilterType.Average: case FilterType.Average:
AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); AverageFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel);
break; break;
case FilterType.Paeth: case FilterType.Paeth:
PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); PaethFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel);
break; break;
default: default:
@ -753,11 +754,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
int length = this.header.Width * 3; 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? // TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length); this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width); PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(compressed.Span, rowSpan, this.header.Width);
} }
} }
else else
@ -770,10 +771,10 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
int length = this.header.Width * 3; 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? // 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>(); Span<Rgb24> rgb24Span = compressed.Span.NonPortableCast<byte, Rgb24>();
for (int x = 0; x < this.header.Width; x++) for (int x = 0; x < this.header.Width; x++)
@ -811,11 +812,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
int length = this.header.Width * 4; 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? // TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length); this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length);
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width); PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(compressed.Span, rowSpan, this.header.Width);
} }
} }
else else
@ -1014,18 +1015,20 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
int length = this.header.Width * 3; 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? // TODO: Should we use pack from vector here instead?
this.From16BitTo8Bit(scanlineBuffer, compressed, length); this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length);
if (this.hasTrans) if (this.hasTrans)
{ {
for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{ {
rgba.R = compressed[o]; rgba.R = compressedSpan[o];
rgba.G = compressed[o + 1]; rgba.G = compressedSpan[o + 1];
rgba.B = compressed[o + 2]; rgba.B = compressedSpan[o + 2];
rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255); rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255);
color.PackFromRgba32(rgba); 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) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3)
{ {
rgba.R = compressed[o]; rgba.R = compressedSpan[o];
rgba.G = compressed[o + 1]; rgba.G = compressedSpan[o + 1];
rgba.B = compressed[o + 2]; rgba.B = compressedSpan[o + 2];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = color;
@ -1082,16 +1085,18 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.BitDepth == 16) if (this.header.BitDepth == 16)
{ {
int length = this.header.Width * 4; 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? // 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) for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4)
{ {
rgba.R = compressed[o]; rgba.R = compressedSpan[o];
rgba.G = compressed[o + 1]; rgba.G = compressedSpan[o + 1];
rgba.B = compressed[o + 2]; rgba.B = compressedSpan[o + 2];
rgba.A = compressed[o + 3]; rgba.A = compressedSpan[o + 3];
color.PackFromRgba32(rgba); color.PackFromRgba32(rgba);
rowSpan[x] = color; rowSpan[x] = color;
@ -1281,7 +1286,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void ReadChunkData(PngChunk chunk) private void ReadChunkData(PngChunk chunk)
{ {
// We rent the buffer here to return it afterwards in Decode() // 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); this.currentStream.Read(chunk.Data.Array, 0, chunk.Length);
} }
@ -1353,7 +1358,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void SwapBuffers() private void SwapBuffers()
{ {
Buffer<byte> temp = this.previousScanline; IManagedByteBuffer temp = this.previousScanline;
this.previousScanline = this.scanline; this.previousScanline = this.scanline;
this.scanline = temp; this.scanline = temp;
} }

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

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

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

@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp
return null; return null;
} }
using (var buffer = config.MemoryManager.Allocate<byte>(maxHeaderSize)) using (IManagedByteBuffer buffer = config.MemoryManager.AllocateManagedByteBuffer(maxHeaderSize))
{ {
long startPosition = stream.Position; long startPosition = stream.Position;
stream.Read(buffer.Array, 0, maxHeaderSize); stream.Read(buffer.Array, 0, maxHeaderSize);
stream.Position = startPosition; 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(); this.Dispose();
} }
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TPixel[] PixelArray => this.PixelBuffer.Buffer.Array;
/// <summary> /// <summary>
/// Gets the size of a single pixel in the number of bytes. /// Gets the size of a single pixel in the number of bytes.
/// </summary> /// </summary>
@ -106,7 +101,7 @@ namespace SixLabors.ImageSharp
public int Height { get; private set; } public int Height { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
Span<TPixel> IBuffer2D<TPixel>.Span => this.PixelBuffer.Span; public Span<TPixel> Span => this.PixelBuffer.Span;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance; private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
@ -122,14 +117,15 @@ namespace SixLabors.ImageSharp
get get
{ {
this.CheckCoordinates(x, y); this.CheckCoordinates(x, y);
return this.PixelArray[(y * this.Width) + x]; return this.Span[(y * this.Width) + x];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.CheckCoordinates(x, y); 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> /// <summary>
/// The underlying buffer containing the raw pixel data. /// The underlying buffer containing the raw pixel data.
/// </summary> /// </summary>
private readonly Buffer<byte> byteBuffer; private readonly IManagedByteBuffer 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);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TPixel}"/> class. /// 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.RowStride = (width * GetComponentCount(componentOrder)) + padding;
this.Length = this.RowStride * height; this.Length = this.RowStride * height;
this.byteBuffer = Configuration.Default.MemoryManager.Allocate<byte>(this.Length, true); this.byteBuffer = Configuration.Default.MemoryManager.AllocateCleanManagedByteBuffer(this.Length);
} }
/// <summary> /// <summary>

2
src/ImageSharp/Memory/ArrayPoolMemoryManager.cs

@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Memory
/// <inheritdoc /> /// <inheritdoc />
internal override void Release<T>(Buffer<T> buffer) 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); this.pool.Return(byteBuffer);
} }
} }

1
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary> /// </summary>
internal static class Buffer2DExtensions internal static class Buffer2DExtensions
{ {
/// <summary> /// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'. /// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary> /// </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(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Buffer.Span;
return ref this.Buffer.Array[(this.Width * y) + x]; 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; private MemoryManager memoryManager;
/// <summary> /// <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> /// </summary>
private IntPtr pointer; private IntPtr pointer;
/// <summary> /// <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> /// </summary>
private GCHandle handle; 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> /// <summary>
/// Initializes a new instance of the <see cref="Buffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary> /// </summary>
@ -35,7 +43,7 @@ namespace SixLabors.ImageSharp.Memory
public Buffer(T[] array) public Buffer(T[] array)
{ {
this.Length = array.Length; this.Length = array.Length;
this.Array = array; this.array = array;
} }
/// <summary> /// <summary>
@ -51,7 +59,7 @@ namespace SixLabors.ImageSharp.Memory
} }
this.Length = length; this.Length = length;
this.Array = array; this.array = array;
} }
internal Buffer(T[] array, int length, MemoryManager memoryManager) internal Buffer(T[] array, int length, MemoryManager memoryManager)
@ -69,20 +77,15 @@ namespace SixLabors.ImageSharp.Memory
} }
/// <summary> /// <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> /// </summary>
public bool IsDisposedOrLostArrayOwnership { get; private set; } public bool IsDisposedOrLostArrayOwnership { get; private set; }
/// <summary> /// <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> /// </summary>
public int Length { get; private set; } public int Length { get; private set; }
/// <summary>
/// Gets the backing pinned array.
/// </summary>
public T[] Array { get; private set; }
/// <summary> /// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer. /// Gets a <see cref="Span{T}"/> to the backing buffer.
/// </summary> /// </summary>
@ -112,7 +115,7 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer) 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> /// <summary>
@ -122,30 +125,7 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Span<T>(Buffer<T> buffer) public static implicit operator Span<T>(Buffer<T> buffer)
{ {
return new Span<T>(buffer.Array, 0, buffer.Length); 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);
} }
/// <summary> /// <summary>
@ -165,17 +145,17 @@ namespace SixLabors.ImageSharp.Memory
this.memoryManager?.Release(this); this.memoryManager?.Release(this);
this.memoryManager = null; this.memoryManager = null;
this.Array = null; this.array = null;
this.Length = 0; this.Length = 0;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary> /// <summary>
/// Unpins <see cref="Array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object. /// 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. /// If <see cref="array"/> is rented, it's the callers responsibility to return it to it's pool.
/// </summary> /// </summary>
/// <returns>The unpinned <see cref="Array"/></returns> /// <returns>The unpinned <see cref="array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] TakeArrayOwnership() public T[] TakeArrayOwnership()
{ {
@ -187,23 +167,14 @@ namespace SixLabors.ImageSharp.Memory
this.IsDisposedOrLostArrayOwnership = true; this.IsDisposedOrLostArrayOwnership = true;
this.UnPin(); this.UnPin();
T[] array = this.Array; T[] array = this.array;
this.Array = null; this.array = null;
this.memoryManager = null; this.memoryManager = null;
return array; return array;
} }
/// <summary> /// <summary>
/// Clears the contents of this buffer. /// Pins <see cref="array"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.Span.Clear();
}
/// <summary>
/// Pins <see cref="Array"/>.
/// </summary> /// </summary>
/// <returns>The pinned pointer</returns> /// <returns>The pinned pointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -217,7 +188,7 @@ namespace SixLabors.ImageSharp.Memory
if (this.pointer == IntPtr.Zero) 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(); this.pointer = this.handle.AddrOfPinnedObject();
} }
@ -225,7 +196,15 @@ namespace SixLabors.ImageSharp.Memory
} }
/// <summary> /// <summary>
/// Unpins <see cref="Array"/>. /// TODO: Refactor this
/// </summary>
internal T[] GetArray()
{
return this.array;
}
/// <summary>
/// Unpins <see cref="array"/>.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnPin() private void UnPin()

2
src/ImageSharp/Memory/ManagedByteBuffer.cs

@ -6,5 +6,7 @@ namespace SixLabors.ImageSharp.Memory
: base(array, length, memoryManager) : 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. /// 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! /// Should be replaced with 'Allocate()' as soon as SixLabors.Shapes has Span-based API-s!
/// </summary> /// </summary>
internal FakeBuffer<T> AllocateFake<T>(int length) internal FakeBuffer<T> AllocateFake<T>(int length, bool dummy = false)
where T : struct where T : struct
{ {
return new FakeBuffer<T>(new T[length]); return new FakeBuffer<T>(new T[length]);

5
src/ImageSharp/Memory/MemoryManagerExtensions.cs

@ -26,6 +26,11 @@
return memoryManager.Allocate<T>(length, true); 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) public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryManager memoryManager, int length)
{ {
return memoryManager.AllocateManagedByteBuffer(length, true); 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); 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) if (MathF.Abs(weight) > Constants.Epsilon)
{ {
float r = Volume(ref this.colorCube[k], this.vmr.Array); float r = Volume(ref this.colorCube[k], this.vmr.Span);
float g = Volume(ref this.colorCube[k], this.vmg.Array); float g = Volume(ref this.colorCube[k], this.vmg.Span);
float b = Volume(ref this.colorCube[k], this.vmb.Array); float b = Volume(ref this.colorCube[k], this.vmb.Span);
float a = Volume(ref this.colorCube[k], this.vma.Array); float a = Volume(ref this.colorCube[k], this.vma.Span);
ref TPixel color = ref this.palette[k]; ref TPixel color = ref this.palette[k];
color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F); 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="cube">The cube.</param>
/// <param name="moment">The moment.</param> /// <param name="moment">The moment.</param>
/// <returns>The result.</returns> /// <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)] return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - 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="direction">The direction.</param>
/// <param name="moment">The moment.</param> /// <param name="moment">The moment.</param>
/// <returns>The result.</returns> /// <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) switch (direction)
{ {
@ -400,7 +400,7 @@ namespace SixLabors.ImageSharp.Quantizers
/// <param name="position">The position.</param> /// <param name="position">The position.</param>
/// <param name="moment">The moment.</param> /// <param name="moment">The moment.</param>
/// <returns>The result.</returns> /// <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) switch (direction)
{ {
@ -548,10 +548,10 @@ namespace SixLabors.ImageSharp.Quantizers
/// <returns>The <see cref="float"/>.</returns> /// <returns>The <see cref="float"/>.</returns>
private float Variance(ref Box cube) private float Variance(ref Box cube)
{ {
float dr = Volume(ref cube, this.vmr.Array); float dr = Volume(ref cube, this.vmr.Span);
float dg = Volume(ref cube, this.vmg.Array); float dg = Volume(ref cube, this.vmg.Span);
float db = Volume(ref cube, this.vmb.Array); float db = Volume(ref cube, this.vmb.Span);
float da = Volume(ref cube, this.vma.Array); float da = Volume(ref cube, this.vma.Span);
float xx = float xx =
this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] 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)]; + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
var vector = new Vector4(dr, dg, db, da); 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> /// <summary>
@ -595,22 +595,22 @@ namespace SixLabors.ImageSharp.Quantizers
/// <returns>The <see cref="float"/>.</returns> /// <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) 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 baseR = Bottom(ref cube, direction, this.vmr.Span);
long baseG = Bottom(ref cube, direction, this.vmg.Array); long baseG = Bottom(ref cube, direction, this.vmg.Span);
long baseB = Bottom(ref cube, direction, this.vmb.Array); long baseB = Bottom(ref cube, direction, this.vmb.Span);
long baseA = Bottom(ref cube, direction, this.vma.Array); long baseA = Bottom(ref cube, direction, this.vma.Span);
long baseW = Bottom(ref cube, direction, this.vwt.Array); long baseW = Bottom(ref cube, direction, this.vwt.Span);
float max = 0F; float max = 0F;
cut = -1; cut = -1;
for (int i = first; i < last; i++) for (int i = first; i < last; i++)
{ {
float halfR = baseR + Top(ref cube, direction, i, this.vmr.Array); float halfR = baseR + Top(ref cube, direction, i, this.vmr.Span);
float halfG = baseG + Top(ref cube, direction, i, this.vmg.Array); float halfG = baseG + Top(ref cube, direction, i, this.vmg.Span);
float halfB = baseB + Top(ref cube, direction, i, this.vmb.Array); float halfB = baseB + Top(ref cube, direction, i, this.vmb.Span);
float halfA = baseA + Top(ref cube, direction, i, this.vma.Array); float halfA = baseA + Top(ref cube, direction, i, this.vma.Span);
float halfW = baseW + Top(ref cube, direction, i, this.vwt.Array); float halfW = baseW + Top(ref cube, direction, i, this.vwt.Span);
if (MathF.Abs(halfW) < Constants.Epsilon) 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> /// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut(ref Box set1, ref Box set2) private bool Cut(ref Box set1, ref Box set2)
{ {
float wholeR = Volume(ref set1, this.vmr.Array); float wholeR = Volume(ref set1, this.vmr.Span);
float wholeG = Volume(ref set1, this.vmg.Array); float wholeG = Volume(ref set1, this.vmg.Span);
float wholeB = Volume(ref set1, this.vmb.Array); float wholeB = Volume(ref set1, this.vmb.Span);
float wholeA = Volume(ref set1, this.vma.Array); float wholeA = Volume(ref set1, this.vma.Span);
float wholeW = Volume(ref set1, this.vwt.Array); 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 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); 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 namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{ {
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
@ -36,12 +37,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void PerElement() public void PerElement()
{ {
Vector4[] s = this.source.Array; ref Vector4 s = ref this.source.Span.DangerousGetPinnableReference();
TPixel[] d = this.destination.Array; ref TPixel d = ref this.destination.Span.DangerousGetPinnableReference();
for (int i = 0; i < this.Count; i++) 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] [Benchmark]
public void PackUsingReferences() public void PackUsingReferences()
{ {
ref Vector4 sp = ref this.source.Array[0]; ref Vector4 sp = ref this.source.DangerousGetPinnableReference();
ref Rgba32 dp = ref this.destination.Array[0]; ref Rgba32 dp = ref this.destination.DangerousGetPinnableReference();
int count = this.Count; int count = this.Count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)

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

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

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

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

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

@ -1,6 +1,9 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
{ {
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -33,8 +36,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void PerElement() public void PerElement()
{ {
TPixel[] s = this.source.Array; Span<TPixel> s = this.source.Span;
byte[] d = this.destination.Array; Span<byte> d = this.destination.Span;
var rgb = default(Rgb24); var rgb = default(Rgb24);
for (int i = 0; i < this.Count; i++) 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)] [Benchmark(Baseline = true)]
public void PerElement() public void PerElement()
{ {
TPixel[] s = this.source.Array; Span<TPixel> s = this.source.Span;
byte[] d = this.destination.Array; Span<byte> d = this.destination.Span;
var rgba = default(Rgba32); var rgba = default(Rgba32);
for (int i = 0; i < this.Count; i++) 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)) 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++) for (int j = 0; j < buffer.Buffer.Length; j++)
{ {
Assert.Equal(0, buffer.Buffer.Array[j]); Assert.Equal(0, span[j]);
buffer.Buffer.Array[j] = 666; 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)) 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]; 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace SixLabors.ImageSharp.Tests.Memory namespace SixLabors.ImageSharp.Tests.Memory
{ {
using System; 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 }; 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(); ref Vector<float> v = ref span.FetchVector();
@ -39,199 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(2, v[2]); Assert.Equal(2, v[2]);
Assert.Equal(3, v[3]); Assert.Equal(3, v[3]);
} }
[Fact] public class SpanHelper_Copy
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
{ {
private static void AssertNotDefault<T>(T[] data, int idx) private static void AssertNotDefault<T>(T[] data, int idx)
where T : struct where T : struct
@ -267,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2);
TestStructs.Foo[] dest = new TestStructs.Foo[count + 5]; TestStructs.Foo[] dest = new TestStructs.Foo[count + 5];
Span<TestStructs.Foo> apSource = new Span<TestStructs.Foo>(source, 1); var apSource = new Span<TestStructs.Foo>(source, 1);
Span<TestStructs.Foo> apDest = new Span<TestStructs.Foo>(dest, 1); var apDest = new Span<TestStructs.Foo>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 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[] source = TestStructs.AlignedFoo.CreateArray(count + 2);
TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5]; TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5];
Span<TestStructs.AlignedFoo> apSource = new Span<TestStructs.AlignedFoo>(source, 1); var apSource = new Span<TestStructs.AlignedFoo>(source, 1);
Span<TestStructs.AlignedFoo> apDest = new Span<TestStructs.AlignedFoo>(dest, 1); var apDest = new Span<TestStructs.AlignedFoo>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 1); SpanHelper.Copy(apSource, apDest, count - 1);
@ -313,8 +124,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
int[] source = CreateTestInts(count + 2); int[] source = CreateTestInts(count + 2);
int[] dest = new int[count + 5]; int[] dest = new int[count + 5];
Span<int> apSource = new Span<int>(source, 1); var apSource = new Span<int>(source, 1);
Span<int> apDest = new Span<int>(dest, 1); var apDest = new Span<int>(dest, 1);
SpanHelper.Copy(apSource, apDest, count - 1); SpanHelper.Copy(apSource, apDest, count - 1);
@ -337,8 +148,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2]; byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2];
Span<TestStructs.Foo> apSource = new Span<TestStructs.Foo>(source, 1); var apSource = new Span<TestStructs.Foo>(source, 1);
Span<byte> apDest = new Span<byte>(dest, sizeof(TestStructs.Foo)); var apDest = new Span<byte>(dest, sizeof(TestStructs.Foo));
SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * 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); TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2]; byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2];
Span<TestStructs.AlignedFoo> apSource = new Span<TestStructs.AlignedFoo>(source, 1); var apSource = new Span<TestStructs.AlignedFoo>(source, 1);
Span<byte> apDest = new Span<byte>(dest, sizeof(TestStructs.AlignedFoo)); var apDest = new Span<byte>(dest, sizeof(TestStructs.AlignedFoo));
SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * 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); int[] source = CreateTestInts(count + 2);
byte[] dest = new byte[destCount + sizeof(int) + 1]; byte[] dest = new byte[destCount + sizeof(int) + 1];
Span<int> apSource = new Span<int>(source); var apSource = new Span<int>(source);
Span<byte> apDest = new Span<byte>(dest); var apDest = new Span<byte>(dest);
SpanHelper.Copy(apSource.AsBytes(), apDest, count * sizeof(int)); SpanHelper.Copy(apSource.AsBytes(), apDest, count * sizeof(int));
@ -404,8 +215,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
byte[] source = CreateTestBytes(srcCount); byte[] source = CreateTestBytes(srcCount);
TestStructs.Foo[] dest = new TestStructs.Foo[count + 2]; TestStructs.Foo[] dest = new TestStructs.Foo[count + 2];
Span<byte> apSource = new Span<byte>(source); var apSource = new Span<byte>(source);
Span<TestStructs.Foo> apDest = new Span<TestStructs.Foo>(dest); var apDest = new Span<TestStructs.Foo>(dest);
SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(TestStructs.Foo)); 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.True((bool)ElementsAreEqual(dest, source, count - 1));
Assert.False((bool)ElementsAreEqual(dest, source, count)); 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) internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index)
{ {
fixed (TestStructs.Foo* pArray = array) fixed (TestStructs.Foo* pArray = array)

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

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

Loading…
Cancel
Save