Browse Source

Merge pull request #1109 from SixLabors/af/disco-buffers

Implement discontiguous buffer handling
af/octree-no-pixelmap
James Jackson-South 6 years ago
committed by GitHub
parent
commit
048b96c3fb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      shared-infrastructure
  2. 155
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  3. 2
      src/ImageSharp/Common/Exceptions/ImageFormatException.cs
  4. 3
      src/ImageSharp/Configuration.cs
  5. 18
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  6. 33
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  7. 16
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  8. 7
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  9. 2
      src/ImageSharp/Formats/IImageDecoder.cs
  10. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
  11. 9
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  12. 4
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  13. 25
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  14. 68
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  15. 12
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  16. 14
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  17. 15
      src/ImageSharp/Formats/Png/PngDecoder.cs
  18. 11
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  19. 17
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  20. 5
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  21. 49
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  22. 2
      src/ImageSharp/Image.Decode.cs
  23. 7
      src/ImageSharp/Image.LoadPixelData.cs
  24. 6
      src/ImageSharp/Image.WrapMemory.cs
  25. 4
      src/ImageSharp/ImageFrame.LoadPixelData.cs
  26. 3
      src/ImageSharp/ImageFrame.cs
  27. 4
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  28. 66
      src/ImageSharp/ImageFrame{TPixel}.cs
  29. 2
      src/ImageSharp/ImageSharp.csproj.DotSettings
  30. 45
      src/ImageSharp/Image{TPixel}.cs
  31. 10
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
  32. 8
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  33. 47
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  34. 11
      src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
  35. 7
      src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
  36. 80
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  37. 134
      src/ImageSharp/Memory/Buffer2D{T}.cs
  38. 26
      src/ImageSharp/Memory/BufferArea{T}.cs
  39. 37
      src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
  40. 232
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
  41. 134
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
  42. 43
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  43. 104
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  44. 190
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  45. 30
      src/ImageSharp/Memory/InvalidMemoryOperationException.cs
  46. 52
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  47. 101
      src/ImageSharp/Memory/MemorySource.cs
  48. 9
      src/ImageSharp/Memory/TransformItemsDelegate{T}.cs
  49. 9
      src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs
  50. 2
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  51. 2
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  52. 2
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  53. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  54. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  55. 13
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  56. 2
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
  57. 182
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  58. 125
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  59. 10
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  60. 47
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  61. 10
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  62. 6
      tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs
  63. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  64. 27
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  65. 8
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  66. 23
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs
  67. 31
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  68. 89
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  69. 3
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  70. 63
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  71. 20
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  72. 78
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  73. 10
      tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
  74. 2
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  75. 25
      tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs
  76. 96
      tests/ImageSharp.Tests/Image/ImageFrameTests.cs
  77. 5
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  78. 81
      tests/ImageSharp.Tests/Image/ImageTests.cs
  79. 21
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  80. 49
      tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs
  81. 4
      tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs
  82. 3
      tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs
  83. 3
      tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs
  84. 199
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  85. 162
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
  86. 120
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs
  87. 67
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs
  88. 128
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs
  89. 111
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs
  90. 107
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs
  91. 84
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs
  92. 214
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
  93. 35
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs
  94. 159
      tests/ImageSharp.Tests/Memory/MemorySourceTests.cs
  95. 8
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  96. 11
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  97. 23
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  98. 10
      tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
  99. 8
      tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs
  100. 13
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

2
shared-infrastructure

@ -1 +1 @@
Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f
Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5

155
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Advanced
=> GetConfiguration((IConfigurationProvider)source);
/// <summary>
/// Gets the configuration .
/// Gets the configuration.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the bounds of the image</returns>
@ -48,15 +49,58 @@ namespace SixLabors.ImageSharp.Advanced
=> source?.Configuration ?? Configuration.Default;
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source));
/// <summary>
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.GetPixelMemory().Span;
{
Guard.NotNull(source, nameof(source));
IMemoryGroup<TPixel> mg = source.GetPixelMemoryGroup();
if (mg.Count > 1)
{
throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
}
return mg.Single().Span;
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
@ -65,9 +109,16 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelSpan();
{
Guard.NotNull(source, nameof(source));
return source.Frames.RootFrame.GetPixelSpan();
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
@ -79,7 +130,13 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowSpan(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@ -91,58 +148,12 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(rowIndex);
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image frame</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer,
/// allowing direct manipulation of pixel data through unsafe operations.
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="ImageFrame{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.PixelBuffer.MemorySource.Memory;
}
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="Image{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.Frames.RootFrame.GetPixelMemory();
return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
@ -153,9 +164,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowMemory(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.PixelBuffer.GetSafeRowMemory(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@ -165,9 +182,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
}
/// <summary>
/// Gets the <see cref="MemoryAllocator"/> assigned to 'source'.
@ -176,15 +199,5 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>Returns the configuration.</returns>
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer.
/// Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
/// <param name="source">The source image frame</param>
/// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
}
}

2
src/ImageSharp/Common/Exceptions/ImageFormatException.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
{
/// <summary>
/// The exception that is thrown when the library tries to load
/// an image, which has an invalid format.
/// an image, which has format or content that is invalid or unsupported by ImageSharp.
/// </summary>
public class ImageFormatException : Exception
{

3
src/ImageSharp/Configuration.cs

@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp
/// The default value is 1MB.
/// </summary>
/// <remarks>
/// Currently only used by Resize.
/// Currently only used by Resize. If the working buffer is expected to be discontiguous,
/// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used.
/// </remarks>
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;

18
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@ -32,7 +33,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
var decoder = new BmpDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
}
}
/// <inheritdoc />

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

@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.options = options;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <summary>
/// Decodes the image from the specified this._stream and sets
/// the data to image.
@ -294,24 +299,27 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean))
using (Buffer2D<bool> undefinedPixels = this.memoryAllocator.Allocate2D<bool>(width, height, AllocationOptions.Clean))
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> undefinedPixels = this.memoryAllocator.Allocate<bool>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> rowsWithUndefinedPixels = this.memoryAllocator.Allocate<bool>(height, AllocationOptions.Clean))
{
Span<bool> rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
if (compression == BmpCompression.RLE8)
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> bufferSpan = buffer.Memory.Span;
if (compression is BmpCompression.RLE8)
{
this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<byte> bufferRow = buffer.GetRowSpan(y);
int rowStartIdx = y * width;
Span<byte> bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
@ -321,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int x = 0; x < width; x++)
{
byte colorIdx = bufferRow[x];
if (undefinedPixels[x, y])
if (undefinedPixelsSpan[rowStartIdx + x])
{
switch (this.options.RleSkippedPixelHandling)
{
@ -372,12 +380,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
TPixel color = default;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * 3, AllocationOptions.Clean))
using (Buffer2D<bool> undefinedPixels = this.memoryAllocator.Allocate2D<bool>(width, height, AllocationOptions.Clean))
using (IMemoryOwner<bool> undefinedPixels = this.memoryAllocator.Allocate<bool>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> rowsWithUndefinedPixels = this.memoryAllocator.Allocate<bool>(height, AllocationOptions.Clean))
{
Span<bool> rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
@ -386,11 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
int rowStartIdx = y * width * 3;
var yMulWidth = y * width;
int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + (x * 3);
if (undefinedPixels[x, y])
if (undefinedPixelsSpan[yMulWidth + x])
{
switch (this.options.RleSkippedPixelHandling)
{

16
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -27,7 +29,19 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : struct, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
/// <inheritdoc/>

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

@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height);
private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator;
/// <summary>

2
src/ImageSharp/Formats/IImageDecoder.cs

@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>;
@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs

@ -139,4 +139,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
}
}
}

9
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -31,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.Component = component;
this.ImagePostProcessor = imagePostProcessor;
this.ColorBuffer = memoryAllocator.Allocate2D<float>(
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
imagePostProcessor.PostProcessorBufferSize.Width,
imagePostProcessor.PostProcessorBufferSize.Height);
imagePostProcessor.PostProcessorBufferSize.Height,
this.blockAreaSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
}
/// <summary>
@ -111,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
}
}
}

4
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y)
public void Convert(ImageFrame<TPixel> frame, int x, int y, in RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame, x, y);
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);

25
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -54,24 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
public void LoadAndStretchEdges<TPixel>(IPixelSource<TPixel> source, int sourceX, int sourceY)
where TPixel : struct, IPixel<TPixel>
{
if (source.PixelBuffer is Buffer2D<T> buffer)
{
this.LoadAndStretchEdges(buffer, sourceX, sourceY);
}
else
{
throw new InvalidOperationException("LoadAndStretchEdges<TPixels>() is only valid for TPixel == T !");
}
}
/// <summary>
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY)
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, in RowOctet<T> currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
@ -85,15 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this);
ref byte imageStart = ref Unsafe.As<T, byte>(
ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX));
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
int imageRowSizeInBytes = source.Width * Unsafe.SizeOf<T>();
for (int y = 0; y < height; y++)
{
ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
Span<T> row = currentRows[y];
ref byte s = ref Unsafe.As<T, byte>(ref row[sourceX]);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
@ -127,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
}
}
}

68
src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Cache 8 pixel rows on the stack, which may originate from different buffers of a <see cref="MemoryGroup{T}"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal readonly ref struct RowOctet<T>
where T : struct
{
private readonly Span<T> row0;
private readonly Span<T> row1;
private readonly Span<T> row2;
private readonly Span<T> row3;
private readonly Span<T> row4;
private readonly Span<T> row5;
private readonly Span<T> row6;
private readonly Span<T> row7;
public RowOctet(Buffer2D<T> buffer, int startY)
{
int y = startY;
int height = buffer.Height;
this.row0 = y < height ? buffer.GetRowSpan(y++) : default;
this.row1 = y < height ? buffer.GetRowSpan(y++) : default;
this.row2 = y < height ? buffer.GetRowSpan(y++) : default;
this.row3 = y < height ? buffer.GetRowSpan(y++) : default;
this.row4 = y < height ? buffer.GetRowSpan(y++) : default;
this.row5 = y < height ? buffer.GetRowSpan(y++) : default;
this.row6 = y < height ? buffer.GetRowSpan(y++) : default;
this.row7 = y < height ? buffer.GetRowSpan(y) : default;
}
public Span<T> this[int y]
{
[MethodImpl(InliningOptions.ShortMethod)]
get
{
// No unsafe tricks, since Span<T> can't be used as a generic argument
return y switch
{
0 => this.row0,
1 => this.row1,
2 => this.row2,
3 => this.row3,
4 => this.row4,
5 => this.row5,
6 => this.row6,
7 => this.row7,
_ => ThrowIndexOutOfRangeException()
};
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static Span<T> ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
}
}

12
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -22,10 +23,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new JpegDecoderCore(configuration, this))
using var decoder = new JpegDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
}
}
/// <inheritdoc />

14
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 8)
{
var currentRows = new RowOctet<TPixel>(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(pixels.Frames.RootFrame, x, y);
pixelConverter.Convert(frame, x, y, currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
@ -935,6 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 16)
{
@ -945,7 +952,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff);
// TODO: Try pushing this to the outer loop!
var currentRows = new RowOctet<TPixel>(pixelBuffer, y + yOff);
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;

15
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@ -44,7 +45,19 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
/// <inheritdoc/>

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

@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private int currentRow = Adam7.FirstRow[0];
/// <summary>
/// The current number of bytes read in the current scanline
/// The current number of bytes read in the current scanline.
/// </summary>
private int currentRowBytesRead;
@ -132,18 +132,23 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.header.Width, this.header.Height);
/// <summary>
/// Decodes the stream to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image data. </param>
/// <param name="stream">The stream containing image data.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the stream does not contain and end chunk.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
/// <returns>The decoded image</returns>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
{

17
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
@ -17,7 +19,20 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Decode<TPixel>(stream);
var decoder = new TgaDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
// TODO: use InvalidImageContentException here, if we decide to define it
// https://github.com/SixLabors/ImageSharp/issues/1110
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
}
}
/// <inheritdoc />

5
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -61,6 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.options = options;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>

49
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
if (this.compression is TgaCompression.RunLength)
{
this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
}
else
{
@ -150,19 +150,20 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The stream to write the image to.</param>
/// <param name="image">The image to encode.</param>
private void WriteRunLengthEndcodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
Rgba32 color = default;
Buffer2D<TPixel> pixels = image.PixelBuffer;
Span<TPixel> pixelSpan = pixels.GetSpan();
int totalPixels = image.Width * image.Height;
int encodedPixels = 0;
while (encodedPixels < totalPixels)
{
TPixel currentPixel = pixelSpan[encodedPixels];
int x = encodedPixels % pixels.Width;
int y = encodedPixels / pixels.Width;
TPixel currentPixel = pixels[x, y];
currentPixel.ToRgba32(ref color);
byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
byte equalPixelCount = this.FindEqualPixels(pixels, x, y);
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
@ -200,30 +201,40 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <summary>
/// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
/// Finds consecutive pixels which have the same value.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixelSpan">The pixel span to search in.</param>
/// <param name="pixels">The pixels of the image.</param>
/// <param name="xStart">X coordinate to start searching for the same pixels.</param>
/// <param name="yStart">Y coordinate to start searching for the same pixels.</param>
/// <returns>The number of equal pixels.</returns>
private byte FindEqualPixels<TPixel>(Span<TPixel> pixelSpan)
private byte FindEqualPixels<TPixel>(Buffer2D<TPixel> pixels, int xStart, int yStart)
where TPixel : struct, IPixel<TPixel>
{
int idx = 0;
byte equalPixelCount = 0;
while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
bool firstRow = true;
TPixel startPixel = pixels[xStart, yStart];
for (int y = yStart; y < pixels.Height; y++)
{
TPixel currentPixel = pixelSpan[idx];
TPixel nextPixel = pixelSpan[idx + 1];
if (currentPixel.Equals(nextPixel))
for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++)
{
equalPixelCount++;
}
else
{
return equalPixelCount;
TPixel nextPixel = pixels[x, y];
if (startPixel.Equals(nextPixel))
{
equalPixelCount++;
}
else
{
return equalPixelCount;
}
if (equalPixelCount >= 127)
{
return equalPixelCount;
}
}
idx++;
firstRow = false;
}
return equalPixelCount;

2
src/ImageSharp/Image.Decode.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp
{
Buffer2D<TPixel> uninitializedMemoryBuffer =
configuration.MemoryAllocator.Allocate2D<TPixel>(width, height);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata);
}
/// <summary>

7
src/ImageSharp/Image.LoadPixelData.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@ -118,10 +119,10 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(config, width, height);
data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan());
data = data.Slice(0, count);
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);
return image;
}
}
}
}

6
src/ImageSharp/Image.WrapMemory.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemory);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemoryOwner, false);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -147,4 +147,4 @@ namespace SixLabors.ImageSharp
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}
}
}

4
src/ImageSharp/ImageFrame.LoadPixelData.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp
var image = new ImageFrame<TPixel>(configuration, width, height);
data.Slice(0, count).CopyTo(image.GetPixelSpan());
data = data.Slice(0, count);
data.CopyTo(image.PixelBuffer.FastMemoryGroup);
return image;
}

3
src/ImageSharp/ImageFrame.cs

@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
internal abstract void CopyPixelsTo<TDestinationPixel>(MemoryGroup<TDestinationPixel> destination)
where TDestinationPixel : struct, IPixel<TDestinationPixel>;
/// <summary>

4
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor));
}
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemorySource<TPixel> memorySource)
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemoryGroup<TPixel> memorySource)
{
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp
this.parent.GetConfiguration(),
source.Size(),
source.Metadata.DeepClone());
source.CopyPixelsTo(result.PixelBuffer.GetSpan());
source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup);
return result;
}
}

66
src/ImageSharp/ImageFrame{TPixel}.cs

@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetadata())
{
}
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the image in pixels.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="metadata">The metadata.</param>
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource, ImageFrameMetadata metadata)
internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup<TPixel> memorySource, ImageFrameMetadata metadata)
: base(configuration, width, height, metadata)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp
Guard.NotNull(source, nameof(source));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D<TPixel>(source.PixelBuffer.Width, source.PixelBuffer.Height);
source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup);
}
/// <summary>
@ -148,13 +148,22 @@ namespace SixLabors.ImageSharp
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the provided (x,y) coordinates are outside the image boundary.</exception>
public TPixel this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.PixelBuffer[x, y];
[MethodImpl(InliningOptions.ShortMethod)]
get
{
this.VerifyCoords(x, y);
return this.PixelBuffer.GetElementUnsafe(x, y);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => this.PixelBuffer[x, y] = value;
[MethodImpl(InliningOptions.ShortMethod)]
set
{
this.VerifyCoords(x, y);
this.PixelBuffer.GetElementUnsafe(x, y) = value;
}
}
/// <summary>
@ -177,7 +186,7 @@ namespace SixLabors.ImageSharp
throw new ArgumentException("ImageFrame<TPixel>.CopyTo(): target must be of the same size!", nameof(target));
}
this.GetPixelSpan().CopyTo(target.GetSpan());
this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup);
}
/// <summary>
@ -209,15 +218,22 @@ namespace SixLabors.ImageSharp
this.isDisposed = true;
}
internal override void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
internal override void CopyPixelsTo<TDestinationPixel>(MemoryGroup<TDestinationPixel> destination)
{
if (typeof(TPixel) == typeof(TDestinationPixel))
{
Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination);
this.PixelBuffer.GetSpan().CopyTo(dest1);
this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) =>
{
Span<TPixel> d1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(d);
s.CopyTo(d1);
});
return;
}
PixelOperations<TPixel>.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSpan(), destination);
this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) =>
{
PixelOperations<TPixel>.Instance.To(this.GetConfiguration(), s, d);
});
}
/// <inheritdoc/>
@ -275,18 +291,38 @@ namespace SixLabors.ImageSharp
/// <param name="value">The value to initialize the bitmap with.</param>
internal void Clear(TPixel value)
{
Span<TPixel> span = this.GetPixelSpan();
MemoryGroup<TPixel> group = this.PixelBuffer.FastMemoryGroup;
if (value.Equals(default))
{
span.Clear();
group.Clear();
}
else
{
span.Fill(value);
group.Fill(value);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private void VerifyCoords(int x, int y)
{
if (x < 0 || x >= this.Width)
{
ThrowArgumentOutOfRangeException(nameof(x));
}
if (y < 0 || y >= this.Height)
{
ThrowArgumentOutOfRangeException(nameof(y));
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentOutOfRangeException(string paramName)
{
throw new ArgumentOutOfRangeException(paramName);
}
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
/// </summary>

2
src/ImageSharp/ImageSharp.csproj.DotSettings

@ -2,6 +2,8 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=color/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=memory_005Callocators/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=memory_005Cdiscontiguousbuffers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pixelformats_005Cgenerated/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pixelformats_005Cpackedpixels/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=pixelformats_005Cpixelimplementations/@EntryIndexedValue">True</s:Boolean>

45
src/ImageSharp/Image{TPixel}.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@ -74,22 +75,22 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// wrapping an external <see cref="MemorySource{T}"/>.
/// wrapping an external <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <param name="memorySource">The memory source.</param>
/// <param name="memoryGroup">The memory source.</param>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(
Configuration configuration,
MemorySource<TPixel> memorySource,
MemoryGroup<TPixel> memoryGroup,
int width,
int height,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
{
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
this.Frames = new ImageFrameCollection<TPixel>(this, width, height, memoryGroup);
}
/// <summary>
@ -144,10 +145,22 @@ namespace SixLabors.ImageSharp
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the provided (x,y) coordinates are outside the image boundary.</exception>
public TPixel this[int x, int y]
{
get => this.PixelSource.PixelBuffer[x, y];
set => this.PixelSource.PixelBuffer[x, y] = value;
[MethodImpl(InliningOptions.ShortMethod)]
get
{
this.VerifyCoords(x, y);
return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y);
}
[MethodImpl(InliningOptions.ShortMethod)]
set
{
this.VerifyCoords(x, y);
this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value;
}
}
/// <summary>
@ -265,5 +278,25 @@ namespace SixLabors.ImageSharp
return rootSize;
}
[MethodImpl(InliningOptions.ShortMethod)]
private void VerifyCoords(int x, int y)
{
if (x < 0 || x >= this.Width)
{
ThrowArgumentOutOfRangeException(nameof(x));
}
if (y < 0 || y >= this.Height)
{
ThrowArgumentOutOfRangeException(nameof(y));
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentOutOfRangeException(string paramName)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
}

10
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -46,7 +46,15 @@ namespace SixLabors.ImageSharp.Memory
protected byte[] Data { get; private set; }
/// <inheritdoc />
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
public override Span<T> GetSpan()
{
if (this.Data == null)
{
throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>");
}
return MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)

8
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs

@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
private const int DefaultNormalPoolBucketCount = 16;
// TODO: This value should be determined by benchmarking
private const int DefaultBufferCapacityInBytes = int.MaxValue / 4;
/// <summary>
/// This is the default. Should be good for most use cases.
/// </summary>
@ -39,7 +42,8 @@ namespace SixLabors.ImageSharp.Memory
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
DefaultNormalPoolBucketCount);
DefaultNormalPoolBucketCount,
DefaultBufferCapacityInBytes);
}
/// <summary>
@ -69,4 +73,4 @@ namespace SixLabors.ImageSharp.Memory
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
}
}

47
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs

@ -60,13 +60,41 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool)
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool)
: this(
maxPoolSizeInBytes,
poolSelectorThresholdInBytes,
maxArraysPerBucketLargePool,
maxArraysPerBucketNormalPool,
DefaultBufferCapacityInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
/// <param name="bufferCapacityInBytes">The length of the largest contiguous buffer that can be handled by this allocator instance.</param>
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool,
int bufferCapacityInBytes)
{
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
this.BufferCapacityInBytes = bufferCapacityInBytes;
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
@ -83,23 +111,30 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public int PoolSelectorThresholdInBytes { get; }
/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance.
/// </summary>
public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
if (bufferSizeInBytes < 0)
if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes)
{
throw new ArgumentOutOfRangeException(
nameof(length),
$"{nameof(ArrayPoolMemoryAllocator)} can not allocate {length} elements of {typeof(T).Name}.");
throw new InvalidMemoryOperationException(
$"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator.");
}
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
@ -147,4 +182,4 @@ namespace SixLabors.ImageSharp.Memory
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
}
}

11
src/ImageSharp/Memory/Allocators/MemoryAllocator.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
@ -10,6 +11,12 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public abstract class MemoryAllocator
{
/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
/// </summary>
/// <returns>The length of the largest contiguous buffer that can be handled by this allocator instance.</returns>
protected internal abstract int GetBufferCapacityInBytes();
/// <summary>
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="System.Memory{T}"/> of length <paramref name="length"/>.
/// </summary>
@ -17,6 +24,8 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="length">Size of the buffer to allocate.</param>
/// <param name="options">The allocation options.</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">When length is zero or negative.</exception>
/// <exception cref="InvalidMemoryOperationException">When length is over the capacity of the allocator.</exception>
public abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
where T : struct;
@ -26,6 +35,8 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="length">The requested buffer length.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="IManagedByteBuffer"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">When length is zero or negative.</exception>
/// <exception cref="InvalidMemoryOperationException">When length is over the capacity of the allocator.</exception>
public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None);
/// <summary>

7
src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs

@ -7,10 +7,13 @@ using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests.
/// Implements <see cref="MemoryAllocator"/> by newing up managed arrays on every allocation request.
/// </summary>
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => int.MaxValue;
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
@ -27,4 +30,4 @@ namespace SixLabors.ImageSharp.Memory
return new BasicByteBuffer(new byte[length]);
}
}
}
}

80
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -14,62 +15,66 @@ namespace SixLabors.ImageSharp.Memory
public static class Buffer2DExtensions
{
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// Gets the backing <see cref="IMemoryGroup{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Span{T}"/> referencing the memory area.</returns>
public static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
/// <param name="buffer">The buffer.</param>
/// <typeparam name="T">The element type.</typeparam>
/// <returns>The MemoryGroup.</returns>
public static IMemoryGroup<T> GetMemoryGroup<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.GetSpan();
return buffer.FastMemoryGroup.View;
}
/// <summary>
/// Gets the <see cref="Memory{T}"/> holding the backing buffer of <paramref name="buffer"/>.
/// Gets a <see cref="Span{T}"/> to the backing data of <paramref name="buffer"/>
/// if the backing group consists of one single contiguous memory buffer.
/// Throws <see cref="InvalidOperationException"/> otherwise.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Memory{T}"/>.</returns>
public static Memory<T> GetMemory<T>(this Buffer2D<T> buffer)
/// <returns>The <see cref="Span{T}"/> referencing the memory area.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the backing group is discontiguous.
/// </exception>
internal static Span<T> GetSingleSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory;
}
if (buffer.FastMemoryGroup.Count > 1)
{
throw new InvalidOperationException("GetSingleSpan is only valid for a single-buffer group!");
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
return buffer.FastMemoryGroup.Single().Span;
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// Gets a <see cref="Memory{T}"/> to the backing data of <paramref name="buffer"/>
/// if the backing group consists of one single contiguous memory buffer.
/// Throws <see cref="InvalidOperationException"/> otherwise.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Memory{T}"/>.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown when the backing group is discontiguous.
/// </exception>
internal static Memory<T> GetSingleMemory<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
if (buffer.FastMemoryGroup.Count > 1)
{
throw new InvalidOperationException("GetSingleMemory is only valid for a single-buffer group!");
}
return buffer.FastMemoryGroup.Single();
}
/// <summary>
/// TODO: Does not work with multi-buffer groups, should be specific to Resize.
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary>
@ -91,7 +96,7 @@ namespace SixLabors.ImageSharp.Memory
int dOffset = destIndex * elementSize;
long count = columnCount * elementSize;
Span<byte> span = MemoryMarshal.AsBytes(buffer.GetMemory().Span);
Span<byte> span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span);
fixed (byte* ptr = span)
{
@ -145,15 +150,6 @@ namespace SixLabors.ImageSharp.Memory
where T : struct =>
new BufferArea<T>(buffer);
/// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary>
internal static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
where T : struct
{
return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}
/// <summary>
/// Returns the size of the buffer.
/// </summary>

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

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory
{
@ -14,23 +15,27 @@ namespace SixLabors.ImageSharp.Memory
/// Before RC1, this class might be target of API changes, use it on your own risk!
/// </remarks>
/// <typeparam name="T">The value type.</typeparam>
// TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core).
public sealed class Buffer2D<T> : IDisposable
where T : struct
{
private MemorySource<T> memorySource;
private Memory<T> cachedMemory = default;
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="memorySource">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
internal Buffer2D(MemorySource<T> memorySource, int width, int height)
/// <param name="memoryGroup">The <see cref="MemoryGroup{T}"/> to wrap.</param>
/// <param name="width">The number of elements in a row.</param>
/// <param name="height">The number of rows.</param>
internal Buffer2D(MemoryGroup<T> memoryGroup, int width, int height)
{
this.memorySource = memorySource;
this.FastMemoryGroup = memoryGroup;
this.Width = width;
this.Height = height;
if (memoryGroup.Count == 1)
{
this.cachedMemory = memoryGroup[0];
}
}
/// <summary>
@ -44,9 +49,20 @@ namespace SixLabors.ImageSharp.Memory
public int Height { get; private set; }
/// <summary>
/// Gets the backing <see cref="MemorySource{T}"/>
/// Gets the backing <see cref="IMemoryGroup{T}"/>.
/// </summary>
internal MemorySource<T> MemorySource => this.memorySource;
/// <returns>The MemoryGroup.</returns>
public IMemoryGroup<T> MemoryGroup => this.FastMemoryGroup.View;
/// <summary>
/// Gets the backing <see cref="MemoryGroup{T}"/> without the view abstraction.
/// </summary>
/// <remarks>
/// This property has been kept internal intentionally.
/// It's public counterpart is <see cref="MemoryGroup"/>,
/// which only exposes the view of the MemoryGroup.
/// </remarks>
internal MemoryGroup<T> FastMemoryGroup { get; }
/// <summary>
/// Gets a reference to the element at the specified position.
@ -54,16 +70,18 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="x">The x coordinate (row)</param>
/// <param name="y">The y coordinate (position at row)</param>
/// <returns>A reference to the element.</returns>
internal ref T this[int x, int y]
/// <exception cref="IndexOutOfRangeException">When index is out of range of the buffer.</exception>
public ref T this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
get
{
DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x));
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.GetSpan();
return ref span[(this.Width * y) + x];
return ref this.GetRowSpan(y)[x];
}
}
@ -72,7 +90,72 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public void Dispose()
{
this.MemorySource.Dispose();
this.FastMemoryGroup.Dispose();
this.cachedMemory = default;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <remarks>
/// This method does not validate the y argument for performance reason,
/// <see cref="ArgumentOutOfRangeException"/> is being propagated from lower levels.
/// </remarks>
/// <param name="y">The row index.</param>
/// <returns>The <see cref="Span{T}"/> of the pixels in the row.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public Span<T> GetRowSpan(int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.cachedMemory.Length > 0
? this.cachedMemory.Span.Slice(y * this.Width, this.Width)
: this.GetRowMemorySlow(y).Span;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal ref T GetElementUnsafe(int x, int y)
{
if (this.cachedMemory.Length > 0)
{
Span<T> span = this.cachedMemory.Span;
ref T start = ref MemoryMarshal.GetReference(span);
return ref Unsafe.Add(ref start, (y * this.Width) + x);
}
return ref this.GetElementSlow(x, y);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// This method is intended for internal use only, since it does not use the indirection provided by
/// <see cref="MemoryGroupView{T}"/>.
/// </summary>
/// <param name="y">The y (row) coordinate.</param>
/// <returns>The <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
internal Memory<T> GetFastRowMemory(int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.cachedMemory.Length > 0
? this.cachedMemory.Slice(y * this.Width, this.Width)
: this.GetRowMemorySlow(y);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="y">The y (row) coordinate.</param>
/// <returns>The <see cref="Span{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
internal Memory<T> GetSafeRowMemory(int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width);
}
/// <summary>
@ -81,11 +164,21 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
internal static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{
MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
SwapDimensionData(destination, source);
bool swap = MemoryGroup<T>.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup);
SwapOwnData(destination, source, swap);
}
[MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)]
private ref T GetElementSlow(int x, int y)
{
Span<T> span = this.GetRowMemorySlow(y).Span;
return ref span[x];
}
private static void SwapDimensionData(Buffer2D<T> a, Buffer2D<T> b)
private static void SwapOwnData(Buffer2D<T> a, Buffer2D<T> b, bool swapCachedMemory)
{
Size aSize = a.Size();
Size bSize = b.Size();
@ -95,6 +188,13 @@ namespace SixLabors.ImageSharp.Memory
a.Width = bSize.Width;
a.Height = bSize.Height;
if (swapCachedMemory)
{
Memory<T> aCached = a.cachedMemory;
a.cachedMemory = b.cachedMemory;
b.cachedMemory = aCached;
}
}
}
}

26
src/ImageSharp/Memory/BufferArea{T}.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory
/// Represents a rectangular area inside a 2D memory buffer (<see cref="Buffer2D{T}"/>).
/// This type is kind-of 2D Span, but it can live on heap.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <typeparam name="T">The element type.</typeparam>
internal readonly struct BufferArea<T>
where T : struct
{
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="x">The position inside a row</param>
/// <param name="y">The row index</param>
/// <returns>The reference to the value</returns>
public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)];
public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y];
/// <summary>
/// Gets a reference to the [0,0] element.
@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Memory
/// <returns>The reference to the [0,0] element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigin() =>
ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];
ref this.GetRowSpan(0)[0];
/// <summary>
/// Gets a span to row 'y' inside this area.
@ -94,16 +94,16 @@ namespace SixLabors.ImageSharp.Memory
int xx = this.Rectangle.X;
int width = this.Rectangle.Width;
return this.DestinationBuffer.GetSpan().Slice(yy + xx, width);
return this.DestinationBuffer.FastMemoryGroup.GetBoundedSlice(yy + xx, width).Span;
}
/// <summary>
/// Returns a sub-area as <see cref="BufferArea{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.)
/// </summary>
/// <param name="x">The x index at the subarea origo</param>
/// <param name="y">The y index at the subarea origo</param>
/// <param name="width">The desired width of the subarea</param>
/// <param name="height">The desired height of the subarea</param>
/// <param name="x">The x index at the subarea origin.</param>
/// <param name="y">The y index at the subarea origin.</param>
/// <param name="width">The desired width of the subarea.</param>
/// <param name="height">The desired height of the subarea.</param>
/// <returns>The subarea</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea<T> GetSubArea(int x, int y, int width, int height)
@ -129,14 +129,6 @@ namespace SixLabors.ImageSharp.Memory
return new BufferArea<T>(this.DestinationBuffer, rectangle);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetIndexOf(int x, int y)
{
int yy = this.GetRowIndex(y);
int xx = this.Rectangle.X + x;
return yy + xx;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int GetRowIndex(int y)
{
@ -148,7 +140,7 @@ namespace SixLabors.ImageSharp.Memory
// Optimization for when the size of the area is the same as the buffer size.
if (this.IsFullBufferArea)
{
this.DestinationBuffer.GetSpan().Clear();
this.DestinationBuffer.FastMemoryGroup.Clear();
return;
}

37
src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs

@ -0,0 +1,37 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Represents discontiguous group of multiple uniformly-sized memory segments.
/// The last segment can be smaller than the preceding ones.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
public interface IMemoryGroup<T> : IReadOnlyList<Memory<T>>
where T : struct
{
/// <summary>
/// Gets the number of elements per contiguous sub-buffer preceding the last buffer.
/// The last buffer is allowed to be smaller.
/// </summary>
public int BufferLength { get; }
/// <summary>
/// Gets the aggregate number of elements in the group.
/// </summary>
public long TotalLength { get; }
/// <summary>
/// Gets a value indicating whether the group has been invalidated.
/// </summary>
/// <remarks>
/// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces
/// the image buffers internally.
/// </remarks>
bool IsValid { get; }
}
}

232
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs

@ -0,0 +1,232 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory
{
internal static class MemoryGroupExtensions
{
internal static void Fill<T>(this IMemoryGroup<T> group, T value)
where T : struct
{
foreach (Memory<T> memory in group)
{
memory.Span.Fill(value);
}
}
internal static void Clear<T>(this IMemoryGroup<T> group)
where T : struct
{
foreach (Memory<T> memory in group)
{
memory.Span.Clear();
}
}
/// <summary>
/// Returns a slice that is expected to be within the bounds of a single buffer.
/// Otherwise <see cref="ArgumentOutOfRangeException"/> is thrown.
/// </summary>
internal static Memory<T> GetBoundedSlice<T>(this IMemoryGroup<T> group, long start, int length)
where T : struct
{
Guard.NotNull(group, nameof(group));
Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!");
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
int bufferIdx = (int)(start / group.BufferLength);
if (bufferIdx >= group.Count)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
int bufferStart = (int)(start % group.BufferLength);
int bufferEnd = bufferStart + length;
Memory<T> memory = group[bufferIdx];
if (bufferEnd > memory.Length)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
return memory.Slice(bufferStart, length);
}
internal static void CopyTo<T>(this IMemoryGroup<T> source, Span<T> target)
where T : struct
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target));
var cur = new MemoryGroupCursor<T>(source);
long position = 0;
while (position < source.TotalLength)
{
int fwd = Math.Min(cur.LookAhead(), target.Length);
cur.GetSpan(fwd).CopyTo(target);
cur.Forward(fwd);
target = target.Slice(fwd);
position += fwd;
}
}
internal static void CopyTo<T>(this Span<T> source, IMemoryGroup<T> target)
where T : struct
=> CopyTo((ReadOnlySpan<T>)source, target);
internal static void CopyTo<T>(this ReadOnlySpan<T> source, IMemoryGroup<T> target)
where T : struct
{
Guard.NotNull(target, nameof(target));
Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target));
var cur = new MemoryGroupCursor<T>(target);
while (!source.IsEmpty)
{
int fwd = Math.Min(cur.LookAhead(), source.Length);
source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd));
cur.Forward(fwd);
source = source.Slice(fwd);
}
}
internal static void CopyTo<T>(this IMemoryGroup<T> source, IMemoryGroup<T> target)
where T : struct
{
Guard.NotNull(source, nameof(source));
Guard.NotNull(target, nameof(target));
Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid.");
Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid.");
Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!");
if (source.IsEmpty())
{
return;
}
long position = 0;
var srcCur = new MemoryGroupCursor<T>(source);
var trgCur = new MemoryGroupCursor<T>(target);
while (position < source.TotalLength)
{
int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead());
Span<T> srcSpan = srcCur.GetSpan(fwd);
Span<T> trgSpan = trgCur.GetSpan(fwd);
srcSpan.CopyTo(trgSpan);
srcCur.Forward(fwd);
trgCur.Forward(fwd);
position += fwd;
}
}
internal static void TransformTo<TSource, TTarget>(
this IMemoryGroup<TSource> source,
IMemoryGroup<TTarget> target,
TransformItemsDelegate<TSource, TTarget> transform)
where TSource : struct
where TTarget : struct
{
Guard.NotNull(source, nameof(source));
Guard.NotNull(target, nameof(target));
Guard.NotNull(transform, nameof(transform));
Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid.");
Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid.");
Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!");
if (source.IsEmpty())
{
return;
}
long position = 0;
var srcCur = new MemoryGroupCursor<TSource>(source);
var trgCur = new MemoryGroupCursor<TTarget>(target);
while (position < source.TotalLength)
{
int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead());
Span<TSource> srcSpan = srcCur.GetSpan(fwd);
Span<TTarget> trgSpan = trgCur.GetSpan(fwd);
transform(srcSpan, trgSpan);
srcCur.Forward(fwd);
trgCur.Forward(fwd);
position += fwd;
}
}
internal static void TransformInplace<T>(
this IMemoryGroup<T> memoryGroup,
TransformItemsInplaceDelegate<T> transform)
where T : struct
{
foreach (Memory<T> memory in memoryGroup)
{
transform(memory.Span);
}
}
internal static bool IsEmpty<T>(this IMemoryGroup<T> group)
where T : struct
=> group.Count == 0;
private struct MemoryGroupCursor<T>
where T : struct
{
private readonly IMemoryGroup<T> memoryGroup;
private int bufferIndex;
private int elementIndex;
public MemoryGroupCursor(IMemoryGroup<T> memoryGroup)
{
this.memoryGroup = memoryGroup;
this.bufferIndex = 0;
this.elementIndex = 0;
}
private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1;
private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length;
public Span<T> GetSpan(int length)
{
return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length);
}
public int LookAhead()
{
return this.CurrentBufferLength - this.elementIndex;
}
public void Forward(int steps)
{
int nextIdx = this.elementIndex + steps;
int currentBufferLength = this.CurrentBufferLength;
if (nextIdx < currentBufferLength)
{
this.elementIndex = nextIdx;
}
else if (nextIdx == currentBufferLength)
{
this.bufferIndex++;
this.elementIndex = 0;
}
else
{
// If we get here, it indicates a bug in CopyTo<T>:
throw new ArgumentException("Can't forward multiple buffers!", nameof(steps));
}
}
}
}
}

134
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs

@ -0,0 +1,134 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Implements <see cref="IMemoryGroup{T}"/>, defining a view for <see cref="Memory.MemoryGroup{T}"/>
/// rather than owning the segments.
/// </summary>
/// <remarks>
/// This type provides an indirection, protecting the users of publicly exposed memory API-s
/// from internal memory-swaps. Whenever an internal swap happens, the <see cref="MemoryGroupView{T}"/>
/// instance becomes invalid, throwing an exception on all operations.
/// </remarks>
/// <typeparam name="T">The element type.</typeparam>
internal class MemoryGroupView<T> : IMemoryGroup<T>
where T : struct
{
private MemoryGroup<T> owner;
private readonly MemoryOwnerWrapper[] memoryWrappers;
public MemoryGroupView(MemoryGroup<T> owner)
{
this.owner = owner;
this.memoryWrappers = new MemoryOwnerWrapper[owner.Count];
for (int i = 0; i < owner.Count; i++)
{
this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i);
}
}
public int Count
{
get
{
this.EnsureIsValid();
return this.owner.Count;
}
}
public int BufferLength
{
get
{
this.EnsureIsValid();
return this.owner.BufferLength;
}
}
public long TotalLength
{
get
{
this.EnsureIsValid();
return this.owner.TotalLength;
}
}
public bool IsValid => this.owner != null;
public Memory<T> this[int index]
{
get
{
this.EnsureIsValid();
return this.memoryWrappers[index].Memory;
}
}
public IEnumerator<Memory<T>> GetEnumerator()
{
this.EnsureIsValid();
for (int i = 0; i < this.Count; i++)
{
yield return this.memoryWrappers[i].Memory;
}
}
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
internal void Invalidate()
{
this.owner = null;
}
private void EnsureIsValid()
{
if (!this.IsValid)
{
throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!");
}
}
private class MemoryOwnerWrapper : MemoryManager<T>
{
private readonly MemoryGroupView<T> view;
private readonly int index;
public MemoryOwnerWrapper(MemoryGroupView<T> view, int index)
{
this.view = view;
this.index = index;
}
protected override void Dispose(bool disposing)
{
}
public override Span<T> GetSpan()
{
this.view.EnsureIsValid();
return this.view.owner[this.index].Span;
}
public override MemoryHandle Pin(int elementIndex = 0)
{
this.view.EnsureIsValid();
return this.view.owner[this.index].Pin();
}
public override void Unpin()
{
throw new NotSupportedException();
}
}
}
}

43
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
namespace SixLabors.ImageSharp.Memory
{
internal abstract partial class MemoryGroup<T>
{
// Analogous to the "consumed" variant of MemorySource
private sealed class Consumed : MemoryGroup<T>
{
private readonly Memory<T>[] source;
public Consumed(Memory<T>[] source, int bufferLength, long totalLength)
: base(bufferLength, totalLength)
{
this.source = source;
this.View = new MemoryGroupView<T>(this);
}
public override int Count => this.source.Length;
public override Memory<T> this[int index] => this.source[index];
public override IEnumerator<Memory<T>> GetEnumerator()
{
for (int i = 0; i < this.source.Length; i++)
{
yield return this.source[i];
}
}
public override void Dispose()
{
this.View.Invalidate();
}
}
}
}

104
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
namespace SixLabors.ImageSharp.Memory
{
// Analogous to the "owned" variant of MemorySource
internal abstract partial class MemoryGroup<T>
{
private sealed class Owned : MemoryGroup<T>
{
private IMemoryOwner<T>[] memoryOwners;
public Owned(IMemoryOwner<T>[] memoryOwners, int bufferLength, long totalLength, bool swappable)
: base(bufferLength, totalLength)
{
this.memoryOwners = memoryOwners;
this.Swappable = swappable;
this.View = new MemoryGroupView<T>(this);
}
public bool Swappable { get; }
private bool IsDisposed => this.memoryOwners == null;
public override int Count
{
get
{
this.EnsureNotDisposed();
return this.memoryOwners.Length;
}
}
public override Memory<T> this[int index]
{
get
{
this.EnsureNotDisposed();
return this.memoryOwners[index].Memory;
}
}
public override IEnumerator<Memory<T>> GetEnumerator()
{
this.EnsureNotDisposed();
return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator();
}
public override void Dispose()
{
if (this.IsDisposed)
{
return;
}
this.View.Invalidate();
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
memoryOwner.Dispose();
}
this.memoryOwners = null;
this.IsValid = false;
}
private void EnsureNotDisposed()
{
if (this.memoryOwners == null)
{
throw new ObjectDisposedException(nameof(MemoryGroup<T>));
}
}
internal static void SwapContents(Owned a, Owned b)
{
a.EnsureNotDisposed();
b.EnsureNotDisposed();
IMemoryOwner<T>[] tempOwners = a.memoryOwners;
long tempTotalLength = a.TotalLength;
int tempBufferLength = a.BufferLength;
a.memoryOwners = b.memoryOwners;
a.TotalLength = b.TotalLength;
a.BufferLength = b.BufferLength;
b.memoryOwners = tempOwners;
b.TotalLength = tempTotalLength;
b.BufferLength = tempBufferLength;
a.View.Invalidate();
b.View.Invalidate();
a.View = new MemoryGroupView<T>(a);
b.View = new MemoryGroupView<T>(b);
}
}
}
}

190
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

@ -0,0 +1,190 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Represents discontinuous group of multiple uniformly-sized memory segments.
/// The underlying buffers may change with time, therefore it's not safe to expose them directly on
/// <see cref="Image{TPixel}"/> and <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal abstract partial class MemoryGroup<T> : IMemoryGroup<T>, IDisposable
where T : struct
{
private static readonly int ElementSize = Unsafe.SizeOf<T>();
private MemoryGroup(int bufferLength, long totalLength)
{
this.BufferLength = bufferLength;
this.TotalLength = totalLength;
}
/// <inheritdoc />
public abstract int Count { get; }
/// <inheritdoc />
public int BufferLength { get; private set; }
/// <inheritdoc />
public long TotalLength { get; private set; }
/// <inheritdoc />
public bool IsValid { get; private set; } = true;
public MemoryGroupView<T> View { get; private set; }
/// <inheritdoc />
public abstract Memory<T> this[int index] { get; }
/// <inheritdoc />
public abstract void Dispose();
/// <inheritdoc />
public abstract IEnumerator<Memory<T>> GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <summary>
/// Creates a new memory group, allocating it's buffers with the provided allocator.
/// </summary>
/// <param name="allocator">The <see cref="MemoryAllocator"/> to use.</param>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
public static MemoryGroup<T> Allocate(
MemoryAllocator allocator,
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
{
Guard.NotNull(allocator, nameof(allocator));
Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength));
Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment));
int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize;
if (bufferAlignment > blockCapacityInElements)
{
throw new InvalidMemoryOperationException(
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}.");
}
if (totalLength == 0)
{
var buffers0 = new IMemoryOwner<T>[1] { allocator.Allocate<T>(0, options) };
return new Owned(buffers0, 0, 0, true);
}
int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment;
int bufferLength = numberOfAlignedSegments * bufferAlignment;
if (totalLength > 0 && totalLength < bufferLength)
{
bufferLength = (int)totalLength;
}
int sizeOfLastBuffer = (int)(totalLength % bufferLength);
long bufferCount = totalLength / bufferLength;
if (sizeOfLastBuffer == 0)
{
sizeOfLastBuffer = bufferLength;
}
else
{
bufferCount++;
}
var buffers = new IMemoryOwner<T>[bufferCount];
for (int i = 0; i < buffers.Length - 1; i++)
{
buffers[i] = allocator.Allocate<T>(bufferLength, options);
}
if (bufferCount > 0)
{
buffers[buffers.Length - 1] = allocator.Allocate<T>(sizeOfLastBuffer, options);
}
return new Owned(buffers, bufferLength, totalLength, true);
}
public static MemoryGroup<T> Wrap(params Memory<T>[] source)
{
int bufferLength = source.Length > 0 ? source[0].Length : 0;
for (int i = 1; i < source.Length - 1; i++)
{
if (source[i].Length != bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!");
}
}
if (source.Length > 0 && source[source.Length - 1].Length > bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!");
}
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Length : 0;
return new Consumed(source, bufferLength, totalLength);
}
public static MemoryGroup<T> Wrap(params IMemoryOwner<T>[] source)
{
int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0;
for (int i = 1; i < source.Length - 1; i++)
{
if (source[i].Memory.Length != bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!");
}
}
if (source.Length > 0 && source[source.Length - 1].Memory.Length > bufferLength)
{
throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!");
}
long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0;
return new Owned(source, bufferLength, totalLength, false);
}
/// <summary>
/// Swaps the contents of 'target' with 'source' if the buffers are allocated (1),
/// copies the contents of 'source' to 'target' otherwise (2).
/// Groups should be of same TotalLength in case 2.
/// </summary>
public static bool SwapOrCopyContent(MemoryGroup<T> target, MemoryGroup<T> source)
{
if (source is Owned ownedSrc && ownedSrc.Swappable &&
target is Owned ownedTarget && ownedTarget.Swappable)
{
Owned.SwapContents(ownedTarget, ownedSrc);
return true;
}
else
{
if (target.TotalLength != source.TotalLength)
{
throw new InvalidMemoryOperationException(
"Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images.");
}
source.CopyTo(target);
return false;
}
}
}
}

30
src/ImageSharp/Memory/InvalidMemoryOperationException.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Exception thrown when the library detects an invalid memory allocation request,
/// or an attempt has been made to use an invalidated <see cref="IMemoryGroup{T}"/>.
/// </summary>
public class InvalidMemoryOperationException : InvalidOperationException
{
/// <summary>
/// Initializes a new instance of the <see cref="InvalidMemoryOperationException"/> class.
/// </summary>
/// <param name="message">The exception message text.</param>
public InvalidMemoryOperationException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="InvalidMemoryOperationException"/> class.
/// </summary>
public InvalidMemoryOperationException()
{
}
}
}

52
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The buffer width.</param>
/// <param name="height">The buffer heght.</param>
/// <param name="height">The buffer height.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
@ -27,10 +27,9 @@ namespace SixLabors.ImageSharp.Memory
AllocationOptions options = AllocationOptions.None)
where T : struct
{
IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>(width * height, options);
var memorySource = new MemorySource<T>(buffer, true);
return new Buffer2D<T>(memorySource, width, height);
long groupLength = (long)width * height;
MemoryGroup<T> memoryGroup = memoryAllocator.AllocateGroup<T>(groupLength, width, options);
return new Buffer2D<T>(memoryGroup, width, height);
}
/// <summary>
@ -49,14 +48,30 @@ namespace SixLabors.ImageSharp.Memory
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);
internal static Buffer2D<T> Allocate2DOveraligned<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
int alignmentMultiplier,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
long groupLength = (long)width * height;
MemoryGroup<T> memoryGroup = memoryAllocator.AllocateGroup<T>(
groupLength,
width * alignmentMultiplier,
options);
return new Buffer2D<T>(memoryGroup, width, height);
}
/// <summary>
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea)
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea).
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/></param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
/// <param name="width">Pixel count in the row</param>
/// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB</param>
/// <param name="paddingInBytes">The padding</param>
/// <returns>A <see cref="IManagedByteBuffer"/></returns>
/// <param name="pixelSizeInBytes">The pixel size in bytes, eg. 3 for RGB.</param>
/// <param name="paddingInBytes">The padding.</param>
/// <returns>A <see cref="IManagedByteBuffer"/>.</returns>
internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
this MemoryAllocator memoryAllocator,
int width,
@ -66,5 +81,22 @@ namespace SixLabors.ImageSharp.Memory
int length = (width * pixelSizeInBytes) + paddingInBytes;
return memoryAllocator.AllocateManagedByteBuffer(length);
}
/// <summary>
/// Allocates a <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use.</param>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
internal static MemoryGroup<T> AllocateGroup<T>(
this MemoryAllocator memoryAllocator,
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(memoryAllocator, totalLength, bufferAlignment, options);
}
}

101
src/ImageSharp/Memory/MemorySource.cs

@ -1,101 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Holds a <see cref="System.Memory{T}"/> that is either OWNED or CONSUMED.
/// When the memory is being owned, the <see cref="IMemoryOwner{T}"/> instance is also known.
/// Implements content transfer logic in <see cref="SwapOrCopyContent"/> that depends on the ownership status.
/// This is needed to transfer the contents of a temporary <see cref="Buffer2D{T}"/>
/// to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/> without copying the buffer.
/// </summary>
/// <remarks>
/// For a deeper understanding of the owner/consumer model, check out the following docs: <br/>
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
/// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T
/// </remarks>
internal struct MemorySource<T> : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="MemorySource{T}"/> struct
/// by wrapping an existing <see cref="IMemoryOwner{T}"/>.
/// </summary>
/// <param name="memoryOwner">The <see cref="IMemoryOwner{T}"/> to wrap</param>
/// <param name="isInternalMemorySource">
/// A value indicating whether <paramref name="memoryOwner"/> is an internal memory source managed by ImageSharp.
/// Eg. allocated by a <see cref="MemoryAllocator"/>.
/// </param>
public MemorySource(IMemoryOwner<T> memoryOwner, bool isInternalMemorySource)
{
this.MemoryOwner = memoryOwner;
this.Memory = memoryOwner.Memory;
this.HasSwappableContents = isInternalMemorySource;
}
public MemorySource(Memory<T> memory)
{
this.Memory = memory;
this.MemoryOwner = null;
this.HasSwappableContents = false;
}
public IMemoryOwner<T> MemoryOwner { get; private set; }
public Memory<T> Memory { get; private set; }
/// <summary>
/// Gets a value indicating whether we are allowed to swap the contents of this buffer
/// with an other <see cref="MemorySource{T}"/> instance.
/// The value is true only and only if <see cref="MemoryOwner"/> is present,
/// and it's coming from an internal source managed by ImageSharp (<see cref="MemoryAllocator"/>).
/// </summary>
public bool HasSwappableContents { get; }
public Span<T> GetSpan() => this.Memory.Span;
public void Clear() => this.Memory.Span.Clear();
/// <summary>
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
/// </summary>
public static void SwapOrCopyContent(ref MemorySource<T> destination, ref MemorySource<T> source)
{
if (source.HasSwappableContents && destination.HasSwappableContents)
{
SwapContents(ref destination, ref source);
}
else
{
if (destination.Memory.Length != source.Memory.Length)
{
throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!");
}
source.Memory.CopyTo(destination.Memory);
}
}
/// <inheritdoc />
public void Dispose()
{
this.MemoryOwner?.Dispose();
}
private static void SwapContents(ref MemorySource<T> a, ref MemorySource<T> b)
{
IMemoryOwner<T> tempOwner = a.MemoryOwner;
Memory<T> tempMemory = a.Memory;
a.MemoryOwner = b.MemoryOwner;
a.Memory = b.Memory;
b.MemoryOwner = tempOwner;
b.Memory = tempMemory;
}
}
}

9
src/ImageSharp/Memory/TransformItemsDelegate{T}.cs

@ -0,0 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory
{
internal delegate void TransformItemsDelegate<TSource, TTarget>(ReadOnlySpan<TSource> source, Span<TTarget> target);
}

9
src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs

@ -0,0 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Memory
{
internal delegate void TransformItemsInplaceDelegate<T>(Span<T> data);
}

2
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}

2
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& this.SourceRectangle == this.cropRectangle)
{
// the cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}

2
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}

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

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

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& sourceRectangle == this.targetRectangle)
{
// The cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}

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

@ -74,10 +74,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.windowBandHeight = verticalKernelMap.MaxDiameter;
// We need to make sure the working buffer is contiguous:
int workingBufferLimitHintInBytes = Math.Min(
configuration.WorkingBufferSizeHintInBytes,
configuration.MemoryAllocator.GetBufferCapacityInBytes());
int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(
this.windowBandHeight,
destWidth,
configuration.WorkingBufferSizeHintInBytes);
workingBufferLimitHintInBytes);
this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight);
@ -113,7 +118,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D<TPixel> destination)
{
Span<Vector4> tempColSpan = this.tempColumnBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan();
// When creating transposedFirstPassBuffer, we made sure it's contiguous:
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan();
for (int y = rowInterval.Min; y < rowInterval.Max; y++)
{
@ -165,7 +172,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private void CalculateFirstPassValues(RowInterval calculationInterval)
{
Span<Vector4> tempRowSpan = this.tempRowBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan();
Span<Vector4> transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan();
for (int y = calculationInterval.Min; y < calculationInterval.Max; y++)
{

2
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (MathF.Abs(degrees) < Constants.Epsilon)
{
// The destination will be blank here so copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return true;
}

182
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -2,9 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers;
using Xunit;
// ReSharper disable InconsistentNaming
@ -12,137 +16,145 @@ namespace SixLabors.ImageSharp.Tests.Advanced
{
public class AdvancedImageExtensionsTests
{
public class GetPixelMemory
public class GetPixelMemoryGroup
{
[Theory]
[WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void WhenMemoryIsOwned<TPixel>(TestImageProvider<TPixel> provider)
[WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)]
[WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)]
[WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)]
public void OwnedMemory_PixelDataIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image0 = provider.GetImage())
{
var targetBuffer = new TPixel[image0.Width * image0.Height];
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
// Act:
Memory<TPixel> memory = image0.GetPixelMemory();
using Image<TPixel> image = provider.GetImage();
// Assert:
Assert.Equal(image0.Width * image0.Height, memory.Length);
memory.Span.CopyTo(targetBuffer);
// Act:
IMemoryGroup<TPixel> memoryGroup = image.GetPixelMemoryGroup();
using (Image<TPixel> image1 = provider.GetImage())
{
// We are using a copy of the original image for assertion
image1.ComparePixelBufferTo(targetBuffer);
}
}
// Assert:
VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size());
}
[Theory]
[WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32 | PixelTypes.Bgr24)]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void WhenMemoryIsConsumed<TPixel>(TestImageProvider<TPixel> provider)
[WithBlankImages(16, 16, PixelTypes.Rgba32)]
public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image0 = provider.GetImage())
{
var targetBuffer = new TPixel[image0.Width * image0.Height];
image0.GetPixelSpan().CopyTo(targetBuffer);
using Image<TPixel> image = provider.GetImage();
var managerOfExternalMemory = new TestMemoryManager<TPixel>(targetBuffer);
IMemoryGroup<TPixel> memoryGroup = image.GetPixelMemoryGroup();
Memory<TPixel> memory = memoryGroup.Single();
Memory<TPixel> externalMemory = managerOfExternalMemory.Memory;
image.Mutate(c => c.Resize(8, 8));
Assert.False(memoryGroup.IsValid);
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memoryGroup.First());
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory.Span);
}
[Theory]
[WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)]
[WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)]
public void ConsumedMemory_PixelDataIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using Image<TPixel> image0 = provider.GetImage();
var targetBuffer = new TPixel[image0.Width * image0.Height];
image0.GetPixelSpan().CopyTo(targetBuffer);
using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height))
{
Memory<TPixel> internalMemory = image1.GetPixelMemory();
Assert.Equal(targetBuffer.Length, internalMemory.Length);
Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0]));
var managerOfExternalMemory = new TestMemoryManager<TPixel>(targetBuffer);
image0.ComparePixelBufferTo(internalMemory.Span);
}
Memory<TPixel> externalMemory = managerOfExternalMemory.Memory;
// Make sure externalMemory works after destruction:
image0.ComparePixelBufferTo(externalMemory.Span);
using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height))
{
VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size());
}
// Make sure externalMemory works after destruction:
VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size());
}
}
[Theory]
[WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void GetPixelRowMemory<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
private static void VerifyMemoryGroupDataMatchesTestPattern<TPixel>(
TestImageProvider<TPixel> provider,
IMemoryGroup<TPixel> memoryGroup,
Size size)
where TPixel : struct, IPixel<TPixel>
{
var targetBuffer = new TPixel[image.Width * image.Height];
Assert.True(memoryGroup.IsValid);
Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength);
Assert.True(memoryGroup.BufferLength % size.Width == 0);
// Act:
for (int y = 0; y < image.Height; y++)
int cnt = 0;
for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++)
{
Memory<TPixel> rowMemory = image.GetPixelRowMemory(y);
rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y));
}
int y = cnt / size.Width;
int x = cnt % size.Width;
// Assert:
using (Image<TPixel> image1 = provider.GetImage())
{
// We are using a copy of the original image for assertion
image1.ComparePixelBufferTo(targetBuffer);
TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y);
TPixel actual = memoryGroup.GetElementAt(i);
Assert.Equal(expected, actual);
}
}
}
[Theory]
[WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public void GetPixelRowSpan<TPixel>(TestImageProvider<TPixel> provider)
[WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)]
[WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)]
[WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)]
public void GetPixelRowMemory_PixelDataIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var targetBuffer = new TPixel[image.Width * image.Height];
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
using Image<TPixel> image = provider.GetImage();
for (int y = 0; y < image.Height; y++)
{
// Act:
for (int y = 0; y < image.Height; y++)
{
Span<TPixel> rowMemory = image.GetPixelRowSpan(y);
rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y));
}
Memory<TPixel> rowMemory = image.GetPixelRowMemory(y);
Span<TPixel> span = rowMemory.Span;
// Assert:
using (Image<TPixel> image1 = provider.GetImage())
for (int x = 0; x < image.Width; x++)
{
// We are using a copy of the original image for assertion
image1.ComparePixelBufferTo(targetBuffer);
Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]);
}
}
}
#pragma warning disable 0618
[Theory]
[WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)]
public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
Memory<TPixel> memory3 = image.GetPixelRowMemory(3);
Memory<TPixel> memory10 = image.GetPixelRowMemory(10);
image.Mutate(c => c.Resize(8, 8));
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory3.Span);
Assert.ThrowsAny<InvalidMemoryOperationException>(() => _ = memory10.Span);
}
[Theory]
[WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)]
public unsafe void DangerousGetPinnableReference_CopyToBuffer<TPixel>(TestImageProvider<TPixel> provider)
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
[WithBlankImages(100, 111, PixelTypes.Rgba32)]
[WithBlankImages(400, 600, PixelTypes.Rgba32)]
public void GetPixelRowSpan_ShouldReferenceSpanOfMemory<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var targetBuffer = new TPixel[image.Width * image.Height];
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
ref byte source = ref Unsafe.As<TPixel, byte>(ref targetBuffer[0]);
ref byte dest = ref Unsafe.As<TPixel, byte>(ref image.DangerousGetPinnableReferenceToPixelBuffer());
fixed (byte* targetPtr = &source)
fixed (byte* pixelBasePtr = &dest)
{
uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf<TPixel>());
Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes);
}
using Image<TPixel> image = provider.GetImage();
image.ComparePixelBufferTo(targetBuffer);
}
Memory<TPixel> memory = image.GetPixelRowMemory(image.Height - 1);
Span<TPixel> span = image.GetPixelRowSpan(image.Height - 1);
Assert.True(span == memory.Span);
}
}
}

125
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -3,8 +3,12 @@
using System;
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
@ -24,6 +28,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public static readonly string[] BitfieldsBmpFiles = BitFields;
private static BmpDecoder BmpDecoder => new BmpDecoder();
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
@ -33,18 +39,46 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
};
[Theory]
[WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_MiscellaneousBitmaps<TPixel>(TestImageProvider<TPixel> provider)
[WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, false)]
[WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, true)]
public void BmpDecoder_CanDecode_MiscellaneousBitmaps<TPixel>(TestImageProvider<TPixel> provider, bool enforceDiscontiguousBuffers)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
image.DebugSave(provider);
TestImageProvider<TPixel> provider = BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
if (!string.IsNullOrEmpty(nonContiguousBuffersStr))
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
}
using Image<TPixel> image = provider.GetImage(BmpDecoder);
image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
}
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
enforceDiscontiguousBuffers ? "Disco" : string.Empty)
.Dispose();
}
[Theory]
[WithFile(Bit32Rgb, PixelTypes.Rgba32)]
[WithFile(Bit16, PixelTypes.Rgba32)]
public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10);
ImageFormatException ex = Assert.Throws<ImageFormatException>(() => provider.GetImage(BmpDecoder));
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
@ -52,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -65,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Inverted<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -78,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_1Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
@ -90,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_4Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
@ -107,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -119,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -131,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -143,7 +177,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_32BitV4Header_Fast<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -218,11 +252,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
[Theory]
[WithFile(RLE8, PixelTypes.Rgba32)]
[WithFile(RLE8Inverted, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(RLE8, PixelTypes.Rgba32, false)]
[WithFile(RLE8Inverted, PixelTypes.Rgba32, false)]
[WithFile(RLE8, PixelTypes.Rgba32, true)]
[WithFile(RLE8Inverted, PixelTypes.Rgba32, true)]
public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit<TPixel>(TestImageProvider<TPixel> provider, bool enforceDiscontiguousBuffers)
where TPixel : struct, IPixel<TPixel>
{
if (enforceDiscontiguousBuffers)
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
{
image.DebugSave(provider);
@ -231,12 +272,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
[Theory]
[WithFile(RLE24, PixelTypes.Rgba32)]
[WithFile(RLE24Cut, PixelTypes.Rgba32)]
[WithFile(RLE24Delta, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(RLE24, PixelTypes.Rgba32, false)]
[WithFile(RLE24Cut, PixelTypes.Rgba32, false)]
[WithFile(RLE24Delta, PixelTypes.Rgba32, false)]
[WithFile(RLE24, PixelTypes.Rgba32, true)]
[WithFile(RLE24Cut, PixelTypes.Rgba32, true)]
[WithFile(RLE24Delta, PixelTypes.Rgba32, true)]
public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit<TPixel>(TestImageProvider<TPixel> provider, bool enforceNonContiguous)
where TPixel : struct, IPixel<TPixel>
{
if (enforceNonContiguous)
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(400);
}
using (Image<TPixel> image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
{
image.DebugSave(provider);
@ -251,7 +300,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
@ -265,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
@ -277,7 +326,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
@ -296,7 +345,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -308,7 +357,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -320,7 +369,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
@ -333,7 +382,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeOversizedPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
@ -350,7 +399,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
Assert.Throws<ImageFormatException>(() =>
{
using (provider.GetImage(new BmpDecoder()))
using (provider.GetImage(BmpDecoder))
{
}
});
@ -364,7 +413,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
Assert.Throws<NotSupportedException>(() =>
{
using (provider.GetImage(new BmpDecoder()))
using (provider.GetImage(BmpDecoder))
{
}
});
@ -375,7 +424,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
@ -387,7 +436,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
@ -399,7 +448,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -412,7 +461,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -424,7 +473,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_RespectsFileHeaderOffset<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -436,7 +485,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -448,7 +497,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
@ -523,7 +572,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2XShortHeader<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
@ -537,7 +586,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);
@ -561,7 +610,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
using (Image<TPixel> image = provider.GetImage(BmpDecoder))
{
image.DebugSave(provider);

10
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -240,6 +240,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
[Theory]
[WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
[WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(100);
TestBmpEncoderCore(provider, bitsPerPixel);
}
private static void TestBmpEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel,

47
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -4,11 +4,16 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
@ -18,6 +23,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;
private static GifDecoder GifDecoder => new GifDecoder();
public static readonly string[] MultiFrameTestFiles =
{
TestImages.Gif.Giphy, TestImages.Gif.Kumin
@ -72,9 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using (var stream = new UnmanagedMemoryStream(data, length))
{
var decoder = new GifDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = GifDecoder.Decode<Rgba32>(Configuration.Default, stream))
{
Assert.Equal((200, 200), (image.Width, image.Height));
}
@ -163,5 +168,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
}
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
[WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)]
public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10);
ImageFormatException ex = Assert.Throws<ImageFormatException>(() => provider.GetImage(GifDecoder));
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
[WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)]
public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
TestImageProvider<TPixel> provider = BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
using Image<TPixel> image = provider.GetImage(GifDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
"Disco")
.Dispose();
}
}
}

10
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -26,10 +26,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
};
[Theory]
[WithTestPatternImages(100, 100, TestPixelTypes)]
public void EncodeGeneratedPatterns<TPixel>(TestImageProvider<TPixel> provider)
[WithTestPatternImages(100, 100, TestPixelTypes, false)]
[WithTestPatternImages(100, 100, TestPixelTypes, false)]
public void EncodeGeneratedPatterns<TPixel>(TestImageProvider<TPixel> provider, bool limitAllocationBuffer)
where TPixel : struct, IPixel<TPixel>
{
if (limitAllocationBuffer)
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
}
using (Image<TPixel> image = provider.GetImage())
{
var encoder = new GifEncoder

6
tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs

@ -41,7 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 0);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet);
TPixel a = s.Frames.RootFrame[0, 0];
TPixel b = d[0, 0];
@ -65,7 +66,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> s = provider.GetImage())
{
var d = default(GenericBlock8x8<TPixel>);
d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7);
var rowOctet = new RowOctet<TPixel>(s.GetRootFramePixelBuffer(), 7);
d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet);
Assert.Equal(s[6, 7], d[0, 0]);
Assert.Equal(s[6, 8], d[0, 1]);

2
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// no need to dispose when buffer is not array owner
var memory = new Memory<float>(values);
var source = new MemorySource<float>(memory);
var source = MemoryGroup<float>.Wrap(memory);
buffers[i] = new Buffer2D<float>(source, values.Length, 1);
}

27
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
@ -12,17 +14,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public partial class JpegDecoderTests
{
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg<TPixel>(TestImageProvider<TPixel> provider)
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgba32, true)]
public void DecodeBaselineJpeg<TPixel>(TestImageProvider<TPixel> provider, bool enforceDiscontiguousBuffers)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump)
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
if (!string.IsNullOrEmpty(nonContiguousBuffersStr))
{
provider.LimitAllocatorBufferCapacity().InPixels(1000 * 8);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(
@ -32,12 +41,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, providerDump).Dispose();
RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty);
// RemoteExecutor.Invoke(
// RunTest,
// providerDump,
// enforceDiscontiguousBuffers ? "Disco" : string.Empty)
// .Dispose();
}
[Theory]
[WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)]
public void UnrecoverableImagesShouldThrowCorrectError<TPixel>(TestImageProvider<TPixel> provider)
public void UnrecoverableImage_Throws_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> => Assert.Throws<ImageFormatException>(provider.GetImage);
}
}

8
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -13,10 +13,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Cmyk,
TestImages.Jpeg.Baseline.Ycck,
TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Turtle420,
TestImages.Jpeg.Baseline.Testorig420,
// BUG: The following image has a high difference compared to the expected output: 1.0096%
// TestImages.Jpeg.Baseline.Jpeg420Small,
TestImages.Jpeg.Baseline.Jpeg420Small,
TestImages.Jpeg.Issues.Fuzz.AccessViolationException922,
TestImages.Jpeg.Baseline.Jpeg444,
TestImages.Jpeg.Baseline.Bad.BadEOF,
@ -95,9 +96,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// Baseline:
[TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100,
[TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100,
[TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100,
[TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100,
[TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100,
[TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100,
[TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100,
// Progressive:
[TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100,
[TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100,

23
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs

@ -14,17 +14,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg";
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
public void DecodeProgressiveJpeg<TPixel>(TestImageProvider<TPixel> provider)
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)]
[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)]
public void DecodeProgressiveJpeg<TPixel>(TestImageProvider<TPixel> provider, bool enforceDiscontiguousBuffers)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump)
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
if (!string.IsNullOrEmpty(nonContiguousBuffersStr))
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(200);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.DebugSave(provider, nonContiguousBuffersStr);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
image.CompareToReferenceOutput(
@ -33,8 +39,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
appendPixelTypeToFileName: false);
}
string dump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, dump).Dispose();
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
enforceDiscontiguousBuffers ? "Disco" : string.Empty)
.Dispose();
}
}
}

31
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -95,19 +95,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump)
{
TestImageProvider<TPixel> provider =
BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false);
}
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(
ImageComparer.Tolerant(BaselineTolerance),
provider,
appendPixelTypeToFileName: false);
}
string dump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(RunTest, dump).Dispose();
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(10);
ImageFormatException ex = Assert.Throws<ImageFormatException>(() => provider.GetImage(JpegDecoder));
this.Output.WriteLine(ex.Message);
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
// DEBUG ONLY!

89
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -3,6 +3,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -15,30 +16,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public class JpegEncoderTests
{
public static readonly TheoryData<string, int> QualityFiles =
new TheoryData<string, int>
{
{ TestImages.Jpeg.Baseline.Calliphora, 80 },
{ TestImages.Jpeg.Progressive.Fb, 75 }
};
new TheoryData<string, int>
{
{ TestImages.Jpeg.Baseline.Calliphora, 80 },
{ TestImages.Jpeg.Progressive.Fb, 75 }
};
public static readonly TheoryData<JpegSubsample, int> BitsPerPixel_Quality =
new TheoryData<JpegSubsample, int>
{
{ JpegSubsample.Ratio420, 40 },
{ JpegSubsample.Ratio420, 60 },
{ JpegSubsample.Ratio420, 100 },
{ JpegSubsample.Ratio444, 40 },
{ JpegSubsample.Ratio444, 60 },
{ JpegSubsample.Ratio444, 100 },
};
new TheoryData<JpegSubsample, int>
{
{ JpegSubsample.Ratio420, 40 },
{ JpegSubsample.Ratio420, 60 },
{ JpegSubsample.Ratio420, 100 },
{ JpegSubsample.Ratio444, 40 },
{ JpegSubsample.Ratio444, 60 },
{ JpegSubsample.Ratio444, 100 },
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio },
{ TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch },
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
new TheoryData<string, int, int, PixelResolutionUnit>
{
{ TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio },
{ TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch },
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
[Theory]
[MemberData(nameof(QualityFiles))]
@ -71,6 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
where TPixel : struct, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality);
@ -79,6 +81,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
where TPixel : struct, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality);
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)]
[WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)]
[WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample)
where TPixel : struct, IPixel<TPixel>
{
ImageComparer comparer = subsample == JpegSubsample.Ratio444
? ImageComparer.TolerantPercentage(0.1f)
: ImageComparer.TolerantPercentage(5f);
provider.LimitAllocatorBufferCapacity().InBytesSqrt(200);
TestJpegEncoderCore(provider, subsample, 100, comparer);
}
/// <summary>
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
/// </summary>
@ -105,25 +123,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static void TestJpegEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
JpegSubsample subsample,
int quality = 100)
int quality = 100,
ImageComparer comparer = null)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using Image<TPixel> image = provider.GetImage();
// There is no alpha in Jpeg!
image.Mutate(c => c.MakeOpaque());
var encoder = new JpegEncoder
{
// There is no alpha in Jpeg!
image.Mutate(c => c.MakeOpaque());
Subsample = subsample,
Quality = quality
};
string info = $"{subsample}-Q{quality}";
var encoder = new JpegEncoder
{
Subsample = subsample,
Quality = quality
};
string info = $"{subsample}-Q{quality}";
ImageComparer comparer = GetComparer(quality, subsample);
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
}
comparer ??= GetComparer(quality, subsample);
// Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
}
[Fact]

3
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"Component{i}: {diff}");
averageDifference += diff.average;
totalDifference += diff.total;
tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length;
tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length;
}
averageDifference /= componentCount;

63
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -2,11 +2,16 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
// ReSharper disable InconsistentNaming
@ -16,6 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
{
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32;
private static PngDecoder PngDecoder => new PngDecoder();
public static readonly string[] CommonTestImages =
{
TestImages.Png.Splash,
@ -87,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
@ -111,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_Interlaced_ImageIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -123,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_48Bpp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -135,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_64Bpp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -147,7 +154,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_L8bitInterlaced<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -159,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_L16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -171,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decode_GrayAlpha16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -183,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_CanDecodeGrey8bitWithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -195,7 +202,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public void Decoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -227,7 +234,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
@ -235,5 +242,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
});
Assert.Null(ex);
}
[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)]
public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10);
ImageFormatException ex = Assert.Throws<ImageFormatException>(() => provider.GetImage(PngDecoder));
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)]
public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
TestImageProvider<TPixel> provider = BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
using Image<TPixel> image = provider.GetImage(PngDecoder);
image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr);
image.CompareToOriginal(provider);
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
"Disco")
.Dispose();
}
}
}

20
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -404,6 +404,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
[Theory]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32)]
[WithTestPatternImages(677, 683, PixelTypes.Rgba32)]
public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
foreach (PngInterlaceMode interlaceMode in InterlaceMode)
{
TestPngEncoderCore(
provider,
PngColorType.Rgb,
PngFilterMethod.Adaptive,
PngBitDepth.Bit8,
interlaceMode,
appendPixelType: true,
appendPngColorType: true);
}
}
private static void TestPngEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
PngColorType pngColorType,

78
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -1,9 +1,12 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
// ReSharper disable InconsistentNaming
@ -13,12 +16,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public class TgaDecoderTests
{
private static TgaDecoder TgaDecoder => new TgaDecoder();
[Theory]
[WithFile(Grey, PixelTypes.Rgba32)]
public void TgaDecoder_CanDecode_Uncompressed_MonoChrome<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -30,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_Uncompressed_15Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -42,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -54,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_Uncompressed_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -66,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -78,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_Uncompressed_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -90,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -102,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -114,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_Uncompressed_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -126,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -138,7 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -150,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -162,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -174,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_WithPalette_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
@ -186,11 +191,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void TgaDecoder_CanDecode_WithPalette_24Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TgaDecoder()))
using (Image<TPixel> image = provider.GetImage(TgaDecoder))
{
image.DebugSave(provider);
TgaTestUtils.CompareWithReferenceDecoder(provider, image);
}
}
[Theory]
[WithFile(Bit16, PixelTypes.Rgba32)]
[WithFile(Bit24, PixelTypes.Rgba32)]
[WithFile(Bit32, PixelTypes.Rgba32)]
public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10);
ImageFormatException ex = Assert.Throws<ImageFormatException>(() => provider.GetImage(TgaDecoder));
Assert.IsType<InvalidMemoryOperationException>(ex.InnerException);
}
[Theory]
[WithFile(Bit24, PixelTypes.Rgba32)]
[WithFile(Bit32, PixelTypes.Rgba32)]
public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
static void RunTest(string providerDump, string nonContiguousBuffersStr)
{
TestImageProvider<TPixel> provider = BasicSerializer.Deserialize<TestImageProvider<TPixel>>(providerDump);
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
using Image<TPixel> image = provider.GetImage(TgaDecoder);
image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr);
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
}
}
string providerDump = BasicSerializer.Serialize(provider);
RemoteExecutor.Invoke(
RunTest,
providerDump,
"Disco")
.Dispose();
}
}
}

10
tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs

@ -122,6 +122,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
public void Encode_Bit32_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
[Theory]
[WithFile(Bit32, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)]
[WithFile(Bit24, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)]
public void Encode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100);
TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
}
private static void TestTgaEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
TgaBitsPerPixel bitsPerPixel,

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

@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
in operation);
// Assert:
TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan());
TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan());
}
}

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

@ -10,31 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{
public class RowIntervalTests
{
[Theory]
[InlineData(10, 20, 5, 10)]
[InlineData(1, 10, 0, 10)]
[InlineData(1, 10, 5, 8)]
[InlineData(1, 1, 0, 1)]
[InlineData(10, 20, 9, 10)]
[InlineData(10, 20, 0, 1)]
public void GetMultiRowSpan(int width, int height, int min, int max)
{
using (Buffer2D<int> buffer = Configuration.Default.MemoryAllocator.Allocate2D<int>(width, height))
{
var rows = new RowInterval(min, max);
Span<int> span = buffer.GetMultiRowSpan(rows);
ref int expected0 = ref buffer.GetSpan()[min * width];
int expectedLength = (max - min) * width;
ref int actual0 = ref span[0];
Assert.Equal(span.Length, expectedLength);
Assert.True(Unsafe.AreSame(ref expected0, ref actual0));
}
}
[Fact]
public void Slice1()
{

96
tests/ImageSharp.Tests/Image/ImageFrameTests.cs

@ -0,0 +1,96 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public class ImageFrameTests
{
public class Indexer
{
private readonly Configuration configuration = Configuration.CreateDefaultInstance();
private void LimitBufferCapacity(int bufferCapacityInBytes)
{
var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator;
allocator.BufferCapacityInBytes = bufferCapacityInBytes;
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GetSet(bool enforceDisco)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
Rgba32 val = frame[3, 4];
Assert.Equal(default(Rgba32), val);
frame[3, 4] = Color.Red;
val = frame[3, 4];
Assert.Equal(Color.Red.ToRgba32(), val);
}
public static TheoryData<bool, int> OutOfRangeData = new TheoryData<bool, int>()
{
{ false, -1 },
{ false, 10 },
{ true, -1 },
{ true, 10 },
};
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Get_OutOfRangeX(bool enforceDisco, int x)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => _ = frame[x, 3]);
Assert.Equal("x", ex.ParamName);
}
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Set_OutOfRangeX(bool enforceDisco, int x)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => frame[x, 3] = default);
Assert.Equal("x", ex.ParamName);
}
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Set_OutOfRangeY(bool enforceDisco, int y)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ImageFrame<Rgba32> frame = image.Frames.RootFrame;
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => frame[3, y] = default);
Assert.Equal("y", ex.ParamName);
}
}
}
}

5
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -116,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
{
Assert.Equal(memory, image.GetPixelMemory());
Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory());
image.GetPixelSpan().Fill(bg);
for (var i = 10; i < 20; i++)
{
@ -151,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
{
Assert.Equal(memoryManager.Memory, image.GetPixelMemory());
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory());
image.GetPixelSpan().Fill(bg);
for (var i = 10; i < 20; i++)

81
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Memory;
@ -85,5 +87,84 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
public class Indexer
{
private readonly Configuration configuration = Configuration.CreateDefaultInstance();
private void LimitBufferCapacity(int bufferCapacityInBytes)
{
var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator;
allocator.BufferCapacityInBytes = bufferCapacityInBytes;
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GetSet(bool enforceDisco)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
Rgba32 val = image[3, 4];
Assert.Equal(default(Rgba32), val);
image[3, 4] = Color.Red;
val = image[3, 4];
Assert.Equal(Color.Red.ToRgba32(), val);
}
public static TheoryData<bool, int> OutOfRangeData = new TheoryData<bool, int>()
{
{ false, -1 },
{ false, 10 },
{ true, -1 },
{ true, 10 },
};
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Get_OutOfRangeX(bool enforceDisco, int x)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => _ = image[x, 3]);
Assert.Equal("x", ex.ParamName);
}
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Set_OutOfRangeX(bool enforceDisco, int x)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[x, 3] = default);
Assert.Equal("x", ex.ParamName);
}
[Theory]
[MemberData(nameof(OutOfRangeData))]
public void Set_OutOfRangeY(bool enforceDisco, int y)
{
if (enforceDisco)
{
this.LimitBufferCapacity(100);
}
using var image = new Image<Rgba32>(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[3, y] = default);
Assert.Equal("y", ex.ParamName);
}
}
}
}

21
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public class LargeImageIntegrationTests
{
[Theory(Skip = "For local testing only.")]
[WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)]
public void CreateAndResize(TestImageProvider<Rgba32> provider)
{
using Image<Rgba32> image = provider.GetImage();
image.Mutate(c => c.Resize(1000, 1000));
image.DebugSave(provider);
}
}
}

49
tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs → tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs

@ -1,18 +1,15 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Win32;
using SixLabors.ImageSharp.Tests;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Memory.Tests
namespace SixLabors.ImageSharp.Tests.Memory.Allocators
{
public class ArrayPoolMemoryAllocatorTests
{
@ -116,13 +113,13 @@ namespace SixLabors.ImageSharp.Memory.Tests
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
using (IMemoryOwner<int> firstAlloc = memoryAllocator.Allocate<int>(42))
{
firstAlloc.GetSpan().Fill(666);
BufferExtensions.GetSpan(firstAlloc).Fill(666);
}
using (IMemoryOwner<int> secondAlloc = memoryAllocator.Allocate<int>(42, options))
{
int expected = options == AllocationOptions.Clean ? 0 : 666;
Assert.Equal(expected, secondAlloc.GetSpan()[0]);
Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]);
}
}
@ -133,7 +130,7 @@ namespace SixLabors.ImageSharp.Memory.Tests
{
MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator;
IMemoryOwner<int> buffer = memoryAllocator.Allocate<int>(32);
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan());
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer));
if (!keepBufferAlive)
{
@ -144,7 +141,7 @@ namespace SixLabors.ImageSharp.Memory.Tests
buffer = memoryAllocator.Allocate<int>(32);
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference()));
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer)));
}
[Fact]
@ -164,12 +161,12 @@ namespace SixLabors.ImageSharp.Memory.Tests
const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int);
IMemoryOwner<int> small = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold - 1);
ref int ptr2Small = ref small.GetReference();
ref int ptr2Small = ref BufferExtensions.GetReference(small);
small.Dispose();
IMemoryOwner<int> large = StaticFixture.MemoryAllocator.Allocate<int>(ArrayLengthThreshold + 1);
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference()));
Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large)));
}
RemoteExecutor.Invoke(RunTest).Dispose();
@ -216,14 +213,34 @@ namespace SixLabors.ImageSharp.Memory.Tests
[Theory]
[InlineData(-1)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
[InlineData(-111)]
public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length)
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
Assert.Equal("length", ex.ParamName);
}
[Fact]
public void AllocateZero()
{
using IMemoryOwner<int> buffer = this.LocalFixture.MemoryAllocator.Allocate<int>(0);
Assert.Equal(0, buffer.Memory.Length);
}
[Theory]
[InlineData(101)]
[InlineData((int.MaxValue / SizeOfLargeStruct) - 1)]
[InlineData(int.MaxValue / SizeOfLargeStruct)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 137)]
public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length)
{
this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct;
Assert.Throws<InvalidMemoryOperationException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
}
[Theory]
[InlineData(-1)]
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
@ -235,7 +252,7 @@ namespace SixLabors.ImageSharp.Memory.Tests
private class MemoryAllocatorFixture
{
public MemoryAllocator MemoryAllocator { get; set; } =
public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } =
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
/// <summary>
@ -245,11 +262,11 @@ namespace SixLabors.ImageSharp.Memory.Tests
where T : struct
{
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length);
ref T ptrToPrevPosition0 = ref buffer.GetReference();
ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer);
buffer.Dispose();
buffer = this.MemoryAllocator.Allocate<T>(length);
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference());
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer));
buffer.Dispose();
return sameBuffers;

4
tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs → tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs

@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory.Tests
namespace SixLabors.ImageSharp.Tests.Memory.Allocators
{
internal static class BufferExtensions
{
@ -22,4 +22,4 @@ namespace SixLabors.ImageSharp.Memory.Tests
where T : struct =>
ref MemoryMarshal.GetReference(buffer.GetSpan());
}
}
}

3
tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs → tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs

@ -5,10 +5,11 @@ using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Memory.Tests
namespace SixLabors.ImageSharp.Tests.Memory.Allocators
{
/// <summary>
/// Inherit this class to test an <see cref="IMemoryOwner{T}"/> implementation (provided by <see cref="MemoryAllocator"/>).

3
tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs → tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs

@ -3,9 +3,10 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Memory.Tests
namespace SixLabors.ImageSharp.Tests.Memory.Allocators
{
public class SimpleGcMemoryAllocatorTests
{

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

@ -3,6 +3,8 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -18,37 +20,74 @@ namespace SixLabors.ImageSharp.Tests.Memory
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(Span<T> span, IMemoryOwner<T> buffer, int bufferOffset = 0)
public static void SpanPointsTo<T>(Span<T> span, Memory<T> buffer, int bufferOffset = 0)
where T : struct
{
ref T actual = ref MemoryMarshal.GetReference(span);
ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset);
ref T expected = ref buffer.Span[bufferOffset];
True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position");
}
}
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
private const int Big = 99999;
[Theory]
[InlineData(Big, 7, 42)]
[InlineData(Big, 1025, 17)]
[InlineData(300, 42, 777)]
public unsafe void Construct(int bufferCapacity, int width, int height)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height);
Assert.Equal(width * height, buffer.FastMemoryGroup.TotalLength);
Assert.True(buffer.FastMemoryGroup.BufferLength % width == 0);
}
}
[Theory]
[InlineData(7, 42)]
[InlineData(1025, 17)]
public void Construct(int width, int height)
[InlineData(Big, 0, 42)]
[InlineData(Big, 1, 0)]
[InlineData(60, 42, 0)]
[InlineData(3, 0, 0)]
public unsafe void Construct_Empty(int bufferCapacity, int width, int height)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height);
Assert.Equal(width * height, buffer.GetMemory().Length);
Assert.Equal(0, buffer.FastMemoryGroup.TotalLength);
Assert.Equal(0, buffer.GetSingleSpan().Length);
}
}
[Theory]
[InlineData(50, 10, 20, 4)]
public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.MemoryAllocator.Allocate2DOveraligned<int>(width, height, alignmentMultiplier);
MemoryGroup<int> memoryGroup = buffer.FastMemoryGroup;
int expectedAlignment = width * alignmentMultiplier;
Assert.Equal(expectedAlignment, memoryGroup.BufferLength);
}
[Fact]
public void CreateClean()
{
using (Buffer2D<int> buffer = this.MemoryAllocator.Allocate2D<int>(42, 42, AllocationOptions.Clean))
{
Span<int> span = buffer.GetSpan();
Span<int> span = buffer.GetSingleSpan();
for (int j = 0; j < span.Length; j++)
{
Assert.Equal(0, span[j]);
@ -57,58 +96,147 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
[Theory]
[InlineData(7, 42, 0)]
[InlineData(7, 42, 10)]
[InlineData(17, 42, 41)]
public void GetRowSpanY(int width, int height, int y)
[InlineData(Big, 7, 42, 0, 0)]
[InlineData(Big, 7, 42, 10, 0)]
[InlineData(Big, 17, 42, 41, 0)]
[InlineData(500, 17, 42, 41, 1)]
[InlineData(200, 100, 30, 1, 0)]
[InlineData(200, 100, 30, 2, 1)]
[InlineData(200, 100, 30, 4, 2)]
public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Span<TestStructs.Foo> span = buffer.GetRowSpan(y);
// Assert.Equal(width * y, span.Start);
Assert.Equal(width, span.Length);
Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y);
int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength);
Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset);
}
}
public static TheoryData<int, int, int, int> GetRowSpanY_OutOfRange_Data = new TheoryData<int, int, int, int>()
{
{ Big, 10, 8, -1 },
{ Big, 10, 8, 8 },
{ 20, 10, 8, -1 },
{ 20, 10, 8, 10 },
};
[Theory]
[MemberData(nameof(GetRowSpanY_OutOfRange_Data))]
public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y)
{
this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity;
using Buffer2D<byte> buffer = this.MemoryAllocator.Allocate2D<byte>(width, height);
Exception ex = Assert.ThrowsAny<Exception>(() => buffer.GetRowSpan(y));
Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException);
}
public static TheoryData<int, int, int, int, int> Indexer_OutOfRange_Data = new TheoryData<int, int, int, int, int>()
{
{ Big, 10, 8, 1, -1 },
{ Big, 10, 8, 1, 8 },
{ Big, 10, 8, -1, 1 },
{ Big, 10, 8, 10, 1 },
{ 20, 10, 8, 1, -1 },
{ 20, 10, 8, 1, 10 },
{ 20, 10, 8, -1, 1 },
{ 20, 10, 8, 10, 1 },
};
[Theory]
[MemberData(nameof(Indexer_OutOfRange_Data))]
public void Indexer_OutOfRange(int bufferCapacity, int width, int height, int x, int y)
{
this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity;
using Buffer2D<byte> buffer = this.MemoryAllocator.Allocate2D<byte>(width, height);
Exception ex = Assert.ThrowsAny<Exception>(() => buffer[x, y]++);
Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException);
}
[Theory]
[InlineData(42, 8, 0, 0)]
[InlineData(400, 1000, 20, 10)]
[InlineData(99, 88, 98, 87)]
public void Indexer(int width, int height, int x, int y)
[InlineData(Big, 42, 8, 0, 0)]
[InlineData(Big, 400, 1000, 20, 10)]
[InlineData(Big, 99, 88, 98, 87)]
[InlineData(500, 200, 30, 42, 13)]
[InlineData(500, 200, 30, 199, 29)]
public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y)
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity;
using (Buffer2D<TestStructs.Foo> buffer = this.MemoryAllocator.Allocate2D<TestStructs.Foo>(width, height))
{
Span<TestStructs.Foo> span = buffer.MemorySource.GetSpan();
int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength;
int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength);
Span<TestStructs.Foo> span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart);
ref TestStructs.Foo actual = ref buffer[x, y];
ref TestStructs.Foo expected = ref span[(y * width) + x];
ref TestStructs.Foo expected = ref span[x];
Assert.True(Unsafe.AreSame(ref expected, ref actual));
}
}
[Fact]
public void SwapOrCopyContent()
public void SwapOrCopyContent_WhenBothAllocated()
{
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5))
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7))
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean))
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean))
{
IMemoryOwner<int> aa = a.MemorySource.MemoryOwner;
IMemoryOwner<int> bb = b.MemorySource.MemoryOwner;
a[1, 3] = 666;
b[1, 3] = 444;
Memory<int> aa = a.FastMemoryGroup.Single();
Memory<int> bb = b.FastMemoryGroup.Single();
Buffer2D<int>.SwapOrCopyContent(a, b);
Assert.Equal(bb, a.MemorySource.MemoryOwner);
Assert.Equal(aa, b.MemorySource.MemoryOwner);
Assert.Equal(bb, a.FastMemoryGroup.Single());
Assert.Equal(aa, b.FastMemoryGroup.Single());
Assert.Equal(new Size(3, 7), a.Size());
Assert.Equal(new Size(10, 5), b.Size());
Assert.Equal(666, b[1, 3]);
Assert.Equal(444, a[1, 3]);
}
}
[Fact]
public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer()
{
using var destData = MemoryGroup<int>.Wrap(new int[100]);
using var dest = new Buffer2D<int>(destData, 10, 10);
using (Buffer2D<int> source = this.MemoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean))
{
source[0, 0] = 1;
dest[0, 0] = 2;
Buffer2D<int>.SwapOrCopyContent(dest, source);
}
int actual1 = dest.GetRowSpan(0)[0];
int actual2 = dest.GetRowSpan(0)[0];
int actual3 = dest.GetSafeRowMemory(0).Span[0];
int actual4 = dest.GetFastRowMemory(0).Span[0];
int actual5 = dest[0, 0];
Assert.Equal(1, actual1);
Assert.Equal(1, actual2);
Assert.Equal(1, actual3);
Assert.Equal(1, actual4);
Assert.Equal(1, actual5);
}
[Theory]
[InlineData(100, 20, 0, 90, 10)]
[InlineData(100, 3, 0, 50, 50)]
@ -121,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(width, height))
{
rnd.RandomFill(b.GetSpan(), 0, 1);
rnd.RandomFill(b.GetSingleSpan(), 0, 1);
b.CopyColumns(startIndex, destIndex, columnCount);
@ -143,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
var rnd = new Random(123);
using (Buffer2D<float> b = this.MemoryAllocator.Allocate2D<float>(100, 100))
{
rnd.RandomFill(b.GetSpan(), 0, 1);
rnd.RandomFill(b.GetSingleSpan(), 0, 1);
b.CopyColumns(0, 50, 22);
b.CopyColumns(0, 50, 22);
@ -159,5 +287,18 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
}
}
[Fact]
public void PublicMemoryGroup_IsMemoryGroupView()
{
using Buffer2D<int> buffer1 = this.MemoryAllocator.Allocate2D<int>(10, 10);
using Buffer2D<int> buffer2 = this.MemoryAllocator.Allocate2D<int>(10, 10);
IMemoryGroup<int> mgBefore = buffer1.MemoryGroup;
Buffer2D<int>.SwapOrCopyContent(buffer1, buffer2);
Assert.False(mgBefore.IsValid);
Assert.NotSame(mgBefore, buffer1.MemoryGroup);
}
}
}

162
tests/ImageSharp.Tests/Memory/BufferAreaTests.cs

@ -9,22 +9,22 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
public class BufferAreaTests
{
private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator();
[Fact]
public void Construct()
{
using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D<int>(10, 20))
{
var rectangle = new Rectangle(3, 2, 5, 6);
var area = new BufferArea<int>(buffer, rectangle);
using Buffer2D<int> buffer = this.memoryAllocator.Allocate2D<int>(10, 20);
var rectangle = new Rectangle(3, 2, 5, 6);
var area = new BufferArea<int>(buffer, rectangle);
Assert.Equal(buffer, area.DestinationBuffer);
Assert.Equal(rectangle, area.Rectangle);
}
Assert.Equal(buffer, area.DestinationBuffer);
Assert.Equal(rectangle, area.Rectangle);
}
private static Buffer2D<int> CreateTestBuffer(int w, int h)
private Buffer2D<int> CreateTestBuffer(int w, int h)
{
var buffer = Configuration.Default.MemoryAllocator.Allocate2D<int>(w, h);
Buffer2D<int> buffer = this.memoryAllocator.Allocate2D<int>(w, h);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
@ -37,110 +37,122 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
[Theory]
[InlineData(2, 3, 2, 2)]
[InlineData(5, 4, 3, 2)]
public void Indexer(int rx, int ry, int x, int y)
[InlineData(1000, 2, 3, 2, 2)]
[InlineData(1000, 5, 4, 3, 2)]
[InlineData(200, 2, 3, 2, 2)]
[InlineData(200, 5, 4, 3, 2)]
public void Indexer(int bufferCapacity, int rx, int ry, int x, int y)
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
var r = new Rectangle(rx, ry, 5, 6);
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
var r = new Rectangle(rx, ry, 5, 6);
BufferArea<int> area = buffer.GetArea(r);
BufferArea<int> area = buffer.GetArea(r);
int value = area[x, y];
int expected = ((ry + y) * 100) + rx + x;
Assert.Equal(expected, value);
}
int value = area[x, y];
int expected = ((ry + y) * 100) + rx + x;
Assert.Equal(expected, value);
}
[Theory]
[InlineData(2, 3, 2, 5, 6)]
[InlineData(5, 4, 3, 6, 5)]
public void GetRowSpan(int rx, int ry, int y, int w, int h)
[InlineData(1000, 2, 3, 2, 5, 6)]
[InlineData(1000, 5, 4, 3, 6, 5)]
[InlineData(200, 2, 3, 2, 5, 6)]
[InlineData(200, 5, 4, 3, 6, 5)]
public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h)
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
var r = new Rectangle(rx, ry, w, h);
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
BufferArea<int> area = buffer.GetArea(r);
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
var r = new Rectangle(rx, ry, w, h);
Span<int> span = area.GetRowSpan(y);
BufferArea<int> area = buffer.GetArea(r);
Assert.Equal(w, span.Length);
Span<int> span = area.GetRowSpan(y);
for (int i = 0; i < w; i++)
{
int expected = ((ry + y) * 100) + rx + i;
int value = span[i];
Assert.Equal(w, span.Length);
Assert.Equal(expected, value);
}
for (int i = 0; i < w; i++)
{
int expected = ((ry + y) * 100) + rx + i;
int value = span[i];
Assert.Equal(expected, value);
}
}
[Fact]
public void GetSubArea()
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10);
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10);
BufferArea<int> area1 = area0.GetSubArea(4, 4, 5, 5);
BufferArea<int> area1 = area0.GetSubArea(4, 4, 5, 5);
var expectedRect = new Rectangle(10, 12, 5, 5);
var expectedRect = new Rectangle(10, 12, 5, 5);
Assert.Equal(buffer, area1.DestinationBuffer);
Assert.Equal(expectedRect, area1.Rectangle);
Assert.Equal(buffer, area1.DestinationBuffer);
Assert.Equal(expectedRect, area1.Rectangle);
int value00 = (12 * 100) + 10;
Assert.Equal(value00, area1[0, 0]);
}
int value00 = (12 * 100) + 10;
Assert.Equal(value00, area1[0, 0]);
}
[Fact]
public void DangerousGetPinnableReference()
[Theory]
[InlineData(1000)]
[InlineData(40)]
public void GetReferenceToOrigin(int bufferCapacity)
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10);
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
ref int r = ref area0.GetReferenceToOrigin();
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10);
int expected = buffer[6, 8];
Assert.Equal(expected, r);
}
ref int r = ref area0.GetReferenceToOrigin();
int expected = buffer[6, 8];
Assert.Equal(expected, r);
}
[Fact]
public void Clear_FullArea()
[Theory]
[InlineData(1000)]
[InlineData(70)]
public void Clear_FullArea(int bufferCapacity)
{
using (Buffer2D<int> buffer = CreateTestBuffer(22, 13))
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.CreateTestBuffer(22, 13);
var emptyRow = new int[22];
buffer.GetArea().Clear();
for (int y = 0; y < 13; y++)
{
buffer.GetArea().Clear();
Span<int> fullSpan = buffer.GetSpan();
Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length]));
Span<int> row = buffer.GetRowSpan(y);
Assert.True(row.SequenceEqual(emptyRow));
}
}
[Fact]
public void Clear_SubArea()
[Theory]
[InlineData(1000)]
[InlineData(40)]
public void Clear_SubArea(int bufferCapacity)
{
using (Buffer2D<int> buffer = CreateTestBuffer(20, 30))
{
BufferArea<int> area = buffer.GetArea(5, 5, 10, 10);
area.Clear();
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
Assert.NotEqual(0, buffer[4, 4]);
Assert.NotEqual(0, buffer[15, 15]);
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area = buffer.GetArea(5, 5, 10, 10);
area.Clear();
Assert.Equal(0, buffer[5, 5]);
Assert.Equal(0, buffer[14, 14]);
Assert.NotEqual(0, buffer[4, 4]);
Assert.NotEqual(0, buffer[15, 15]);
for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++)
{
Span<int> span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width);
Assert.True(span.SequenceEqual(new int[area.Width]));
}
Assert.Equal(0, buffer[5, 5]);
Assert.Equal(0, buffer[14, 14]);
for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++)
{
Span<int> span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width);
Assert.True(span.SequenceEqual(new int[area.Width]));
}
}
}

120
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs

@ -0,0 +1,120 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public struct MemoryGroupIndex : IEquatable<MemoryGroupIndex>
{
public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other);
public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex);
public int BufferLength { get; }
public int BufferIndex { get; }
public int ElementIndex { get; }
public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex)
{
this.BufferLength = bufferLength;
this.BufferIndex = bufferIndex;
this.ElementIndex = elementIndex;
}
public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val)
{
int nextElementIndex = idx.ElementIndex + val;
return new MemoryGroupIndex(
idx.BufferLength,
idx.BufferIndex + (nextElementIndex / idx.BufferLength),
nextElementIndex % idx.BufferLength);
}
public bool Equals(MemoryGroupIndex other)
{
if (this.BufferLength != other.BufferLength)
{
throw new InvalidOperationException();
}
return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex;
}
public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b);
public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b);
public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b)
{
if (a.BufferLength != b.BufferLength)
{
throw new InvalidOperationException();
}
if (a.BufferIndex < b.BufferIndex)
{
return true;
}
if (a.BufferIndex == b.BufferIndex)
{
return a.ElementIndex < b.ElementIndex;
}
return false;
}
public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b)
{
if (a.BufferLength != b.BufferLength)
{
throw new InvalidOperationException();
}
if (a.BufferIndex > b.BufferIndex)
{
return true;
}
if (a.BufferIndex == b.BufferIndex)
{
return a.ElementIndex > b.ElementIndex;
}
return false;
}
}
internal static class MemoryGroupIndexExtensions
{
public static T GetElementAt<T>(this IMemoryGroup<T> group, MemoryGroupIndex idx)
where T : struct
{
return group[idx.BufferIndex].Span[idx.ElementIndex];
}
public static void SetElementAt<T>(this IMemoryGroup<T> group, MemoryGroupIndex idx, T value)
where T : struct
{
group[idx.BufferIndex].Span[idx.ElementIndex] = value;
}
public static MemoryGroupIndex MinIndex<T>(this IMemoryGroup<T> group)
where T : struct
{
return new MemoryGroupIndex(group.BufferLength, 0, 0);
}
public static MemoryGroupIndex MaxIndex<T>(this IMemoryGroup<T> group)
where T : struct
{
return group.Count == 0
? new MemoryGroupIndex(group.BufferLength, 0, 0)
: new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length);
}
}
}

67
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs

@ -0,0 +1,67 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public class MemoryGroupIndexTests
{
[Fact]
public void Equal()
{
var a = new MemoryGroupIndex(10, 1, 3);
var b = new MemoryGroupIndex(10, 1, 3);
Assert.True(a.Equals(b));
Assert.True(a == b);
Assert.False(a != b);
Assert.False(a < b);
Assert.False(a > b);
}
[Fact]
public void SmallerBufferIndex()
{
var a = new MemoryGroupIndex(10, 3, 3);
var b = new MemoryGroupIndex(10, 5, 3);
Assert.False(a == b);
Assert.True(a != b);
Assert.True(a < b);
Assert.False(a > b);
}
[Fact]
public void SmallerElementIndex()
{
var a = new MemoryGroupIndex(10, 3, 3);
var b = new MemoryGroupIndex(10, 3, 9);
Assert.False(a == b);
Assert.True(a != b);
Assert.True(a < b);
Assert.False(a > b);
}
[Fact]
public void Increment()
{
var a = new MemoryGroupIndex(10, 3, 3);
a += 1;
Assert.Equal(new MemoryGroupIndex(10, 3, 4), a);
}
[Fact]
public void Increment_OverflowBuffer()
{
var a = new MemoryGroupIndex(10, 5, 3);
var b = new MemoryGroupIndex(10, 5, 9);
a += 8;
b += 1;
Assert.Equal(new MemoryGroupIndex(10, 6, 1), a);
Assert.Equal(new MemoryGroupIndex(10, 6, 0), b);
}
}
}

128
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs

@ -0,0 +1,128 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public partial class MemoryGroupTests
{
public class Allocate : MemoryGroupTestsBase
{
#pragma warning disable SA1509
public static TheoryData<object, int, int, long, int, int, int> AllocateData =
new TheoryData<object, int, int, long, int, int, int>()
{
{ default(S5), 22, 4, 4, 1, 4, 4 },
{ default(S5), 22, 4, 7, 2, 4, 3 },
{ default(S5), 22, 4, 8, 2, 4, 4 },
{ default(S5), 22, 4, 21, 6, 4, 1 },
// empty:
{ default(S5), 22, 0, 0, 1, -1, 0 },
{ default(S5), 22, 4, 0, 1, -1, 0 },
{ default(S4), 50, 12, 12, 1, 12, 12 },
{ default(S4), 50, 7, 12, 2, 7, 5 },
{ default(S4), 50, 6, 12, 1, 12, 12 },
{ default(S4), 50, 5, 12, 2, 10, 2 },
{ default(S4), 50, 4, 12, 1, 12, 12 },
{ default(S4), 50, 3, 12, 1, 12, 12 },
{ default(S4), 50, 2, 12, 1, 12, 12 },
{ default(S4), 50, 1, 12, 1, 12, 12 },
{ default(S4), 50, 12, 13, 2, 12, 1 },
{ default(S4), 50, 7, 21, 3, 7, 7 },
{ default(S4), 50, 7, 23, 4, 7, 2 },
{ default(S4), 50, 6, 13, 2, 12, 1 },
{ default(short), 200, 50, 49, 1, 49, 49 },
{ default(short), 200, 50, 1, 1, 1, 1 },
{ default(byte), 1000, 512, 2047, 4, 512, 511 }
};
[Theory]
[MemberData(nameof(AllocateData))]
public void BufferSizesAreCorrect<T>(
T dummy,
int bufferCapacity,
int bufferAlignment,
long totalLength,
int expectedNumberOfBuffers,
int expectedBufferSize,
int expectedSizeOfLastBuffer)
where T : struct
{
this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity;
// Act:
using var g = MemoryGroup<T>.Allocate(this.MemoryAllocator, totalLength, bufferAlignment);
// Assert:
Assert.Equal(expectedNumberOfBuffers, g.Count);
if (expectedBufferSize >= 0)
{
Assert.Equal(expectedBufferSize, g.BufferLength);
}
if (g.Count == 0)
{
return;
}
for (int i = 0; i < g.Count - 1; i++)
{
Assert.Equal(g[i].Length, expectedBufferSize);
}
Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer);
}
[Fact]
public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException()
{
this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16
Assert.Throws<InvalidMemoryOperationException>(() =>
{
MemoryGroup<short>.Allocate(this.MemoryAllocator, 50, 43);
});
}
[Theory]
[InlineData(AllocationOptions.None)]
[InlineData(AllocationOptions.Clean)]
public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions)
{
this.MemoryAllocator.BufferCapacityInBytes = 200;
HashSet<int> bufferHashes;
int expectedBlockCount = 5;
using (var g = MemoryGroup<short>.Allocate(this.MemoryAllocator, 500, 100, allocationOptions))
{
IReadOnlyList<TestMemoryAllocator.AllocationRequest> allocationLog = this.MemoryAllocator.AllocationLog;
Assert.Equal(expectedBlockCount, allocationLog.Count);
bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet();
Assert.Equal(expectedBlockCount, bufferHashes.Count);
Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count);
for (int i = 0; i < expectedBlockCount; i++)
{
Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions);
Assert.Equal(100, allocationLog[i].Length);
Assert.Equal(200, allocationLog[i].LengthInBytes);
}
}
Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count);
Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer)));
}
}
}
}

111
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs

@ -0,0 +1,111 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public partial class MemoryGroupTests
{
public class CopyTo : MemoryGroupTestsBase
{
public static readonly TheoryData<int, int, int, int> WhenSourceBufferIsShorterOrEqual_Data =
CopyAndTransformData;
[Theory]
[MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))]
public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen)
{
using MemoryGroup<int> src = this.CreateTestGroup(srcTotal, srcBufLen, true);
using MemoryGroup<int> trg = this.CreateTestGroup(trgTotal, trgBufLen, false);
src.CopyTo(trg);
int pos = 0;
MemoryGroupIndex i = src.MinIndex();
MemoryGroupIndex j = trg.MinIndex();
for (; i < src.MaxIndex(); i += 1, j += 1, pos++)
{
int a = src.GetElementAt(i);
int b = trg.GetElementAt(j);
Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}");
}
}
[Fact]
public void WhenTargetBufferTooShort_Throws()
{
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true);
using MemoryGroup<int> trg = this.CreateTestGroup(5, 20, false);
Assert.Throws<ArgumentOutOfRangeException>(() => src.CopyTo(trg));
}
[Theory]
[InlineData(30, 10, 40)]
[InlineData(42, 23, 42)]
[InlineData(1, 3, 10)]
[InlineData(0, 4, 0)]
public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength)
{
using MemoryGroup<int> src = this.CreateTestGroup(totalLength, bufferLength, true);
var trg = new int[spanLength];
src.CopyTo(trg);
int expected = 1;
foreach (int val in trg.AsSpan().Slice(0, (int)totalLength))
{
Assert.Equal(expected, val);
expected++;
}
}
[Theory]
[InlineData(20, 7, 19)]
[InlineData(2, 1, 1)]
public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength)
{
using MemoryGroup<int> src = this.CreateTestGroup(totalLength, bufferLength, true);
var trg = new int[spanLength];
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => src.CopyTo(trg));
}
[Theory]
[InlineData(30, 35, 10)]
[InlineData(42, 23, 42)]
[InlineData(10, 3, 1)]
[InlineData(0, 3, 0)]
public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength)
{
var src = new int[spanLength];
for (int i = 0; i < src.Length; i++)
{
src[i] = i + 1;
}
using MemoryGroup<int> trg = this.CreateTestGroup(totalLength, bufferLength);
src.AsSpan().CopyTo(trg);
int position = 0;
for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++)
{
int expected = position + 1;
Assert.Equal(expected, trg.GetElementAt(i));
}
}
[Theory]
[InlineData(10, 3, 11)]
[InlineData(0, 3, 1)]
public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength)
{
var src = new int[spanLength];
using MemoryGroup<int> trg = this.CreateTestGroup(totalLength, bufferLength, true);
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => src.AsSpan().CopyTo(trg));
}
}
}
}

107
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs

@ -0,0 +1,107 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public partial class MemoryGroupTests
{
public class SwapOrCopyContent : MemoryGroupTestsBase
{
[Fact]
public void WhenBothAreMemoryOwners_ShouldSwap()
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50;
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 50);
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 50);
Memory<int> a0 = a[0];
Memory<int> a1 = a[1];
Memory<int> b0 = b[0];
Memory<int> b1 = b[1];
bool swap = MemoryGroup<int>.SwapOrCopyContent(a, b);
Assert.True(swap);
Assert.Equal(b0, a[0]);
Assert.Equal(b1, a[1]);
Assert.Equal(a0, b[0]);
Assert.Equal(a1, b[1]);
Assert.NotEqual(a[0], b[0]);
}
[Fact]
public void WhenBothAreMemoryOwners_ShouldReplaceViews()
{
using MemoryGroup<int> a = this.MemoryAllocator.AllocateGroup<int>(100, 100);
using MemoryGroup<int> b = this.MemoryAllocator.AllocateGroup<int>(120, 100);
a[0].Span[42] = 1;
b[0].Span[33] = 2;
MemoryGroupView<int> aView0 = a.View;
MemoryGroupView<int> bView0 = b.View;
MemoryGroup<int>.SwapOrCopyContent(a, b);
Assert.False(aView0.IsValid);
Assert.False(bView0.IsValid);
Assert.ThrowsAny<InvalidOperationException>(() => _ = aView0[0].Span);
Assert.ThrowsAny<InvalidOperationException>(() => _ = bView0[0].Span);
Assert.True(a.View.IsValid);
Assert.True(b.View.IsValid);
Assert.Equal(2, a.View[0].Span[33]);
Assert.Equal(1, b.View[0].Span[42]);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated)
{
var data = new Rgba32[21];
var color = new Rgba32(1, 2, 3, 4);
using var destOwner = new TestMemoryManager<Rgba32>(data);
using var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory);
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(21, 30);
source[0].Span[10] = color;
// Act:
bool swap = MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source);
// Assert:
Assert.False(swap);
Assert.Equal(color, dest[0].Span[10]);
Assert.NotEqual(source[0], dest[0]);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner)
{
var data = new Rgba32[21];
var color = new Rgba32(1, 2, 3, 4);
using var destOwner = new TestMemoryManager<Rgba32>(data);
var dest = MemoryGroup<Rgba32>.Wrap(destOwner.Memory);
using MemoryGroup<Rgba32> source = this.MemoryAllocator.AllocateGroup<Rgba32>(22, 30);
source[0].Span[10] = color;
// Act:
Assert.ThrowsAny<InvalidOperationException>(() => MemoryGroup<Rgba32>.SwapOrCopyContent(dest, source));
Assert.Equal(color, source[0].Span[10]);
Assert.NotEqual(color, dest[0].Span[10]);
}
}
}
}

84
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs

@ -0,0 +1,84 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public partial class MemoryGroupTests
{
public class View : MemoryGroupTestsBase
{
[Fact]
public void RefersToOwnerGroupContent()
{
using MemoryGroup<int> group = this.CreateTestGroup(240, 80, true);
MemoryGroupView<int> view = group.View;
Assert.True(view.IsValid);
Assert.Equal(group.Count, view.Count);
Assert.Equal(group.BufferLength, view.BufferLength);
Assert.Equal(group.TotalLength, view.TotalLength);
int cnt = 1;
foreach (Memory<int> memory in view)
{
Span<int> span = memory.Span;
foreach (int t in span)
{
Assert.Equal(cnt, t);
cnt++;
}
}
}
[Fact]
public void IsInvalidatedOnOwnerGroupDispose()
{
MemoryGroupView<int> view;
using (MemoryGroup<int> group = this.CreateTestGroup(240, 80, true))
{
view = group.View;
}
Assert.False(view.IsValid);
Assert.ThrowsAny<InvalidMemoryOperationException>(() =>
{
_ = view.Count;
});
Assert.ThrowsAny<InvalidMemoryOperationException>(() =>
{
_ = view.BufferLength;
});
Assert.ThrowsAny<InvalidMemoryOperationException>(() =>
{
_ = view.TotalLength;
});
Assert.ThrowsAny<InvalidMemoryOperationException>(() =>
{
_ = view[0];
});
}
[Fact]
public void WhenInvalid_CanNotUseMemberMemory()
{
Memory<int> memory;
using (MemoryGroup<int> group = this.CreateTestGroup(240, 80, true))
{
memory = group.View[0];
}
Assert.ThrowsAny<InvalidMemoryOperationException>(() =>
{
_ = memory.Span;
});
}
}
}
}

214
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs

@ -0,0 +1,214 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public partial class MemoryGroupTests : MemoryGroupTestsBase
{
[Fact]
public void IsValid_TrueAfterCreation()
{
using var g = MemoryGroup<byte>.Allocate(this.MemoryAllocator, 10, 100);
Assert.True(g.IsValid);
}
[Fact]
public void IsValid_FalseAfterDisposal()
{
using var g = MemoryGroup<byte>.Allocate(this.MemoryAllocator, 10, 100);
g.Dispose();
Assert.False(g.IsValid);
}
#pragma warning disable SA1509
private static readonly TheoryData<int, int, int, int> CopyAndTransformData =
new TheoryData<int, int, int, int>()
{
{ 20, 10, 20, 10 },
{ 20, 5, 20, 4 },
{ 20, 4, 20, 5 },
{ 18, 6, 20, 5 },
{ 19, 10, 20, 10 },
{ 21, 10, 22, 2 },
{ 1, 5, 5, 4 },
{ 30, 12, 40, 5 },
{ 30, 5, 40, 12 },
};
public class TransformTo : MemoryGroupTestsBase
{
public static readonly TheoryData<int, int, int, int> WhenSourceBufferIsShorterOrEqual_Data =
CopyAndTransformData;
[Theory]
[MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))]
public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen)
{
using MemoryGroup<int> src = this.CreateTestGroup(srcTotal, srcBufLen, true);
using MemoryGroup<int> trg = this.CreateTestGroup(trgTotal, trgBufLen, false);
src.TransformTo(trg, MultiplyAllBy2);
int pos = 0;
MemoryGroupIndex i = src.MinIndex();
MemoryGroupIndex j = trg.MinIndex();
for (; i < src.MaxIndex(); i += 1, j += 1, pos++)
{
int a = src.GetElementAt(i);
int b = trg.GetElementAt(j);
Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}");
}
}
[Fact]
public void WhenTargetBufferTooShort_Throws()
{
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true);
using MemoryGroup<int> trg = this.CreateTestGroup(5, 20, false);
Assert.Throws<ArgumentOutOfRangeException>(() => src.TransformTo(trg, MultiplyAllBy2));
}
}
[Theory]
[InlineData(100, 5)]
[InlineData(100, 101)]
public void TransformInplace(int totalLength, int bufferLength)
{
using MemoryGroup<int> src = this.CreateTestGroup(10, 20, true);
src.TransformInplace(s => MultiplyAllBy2(s, s));
int cnt = 1;
for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1)
{
int val = src.GetElementAt(i);
Assert.Equal(expected: cnt * 2, val);
cnt++;
}
}
[Fact]
public void Wrap()
{
int[] data0 = { 1, 2, 3, 4 };
int[] data1 = { 5, 6, 7, 8 };
int[] data2 = { 9, 10 };
using var mgr0 = new TestMemoryManager<int>(data0);
using var mgr1 = new TestMemoryManager<int>(data1);
using var group = MemoryGroup<int>.Wrap(mgr0.Memory, mgr1.Memory, data2);
Assert.Equal(3, group.Count);
Assert.Equal(4, group.BufferLength);
Assert.Equal(10, group.TotalLength);
Assert.True(group[0].Span.SequenceEqual(data0));
Assert.True(group[1].Span.SequenceEqual(data1));
Assert.True(group[2].Span.SequenceEqual(data2));
}
public static TheoryData<long, int, long, int> GetBoundedSlice_SuccessData = new TheoryData<long, int, long, int>()
{
{ 300, 100, 110, 80 },
{ 300, 100, 100, 100 },
{ 280, 100, 201, 79 },
{ 42, 7, 0, 0 },
{ 42, 7, 0, 1 },
{ 42, 7, 0, 7 },
{ 42, 9, 9, 9 },
};
[Theory]
[MemberData(nameof(GetBoundedSlice_SuccessData))]
public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length)
{
using MemoryGroup<int> group = this.CreateTestGroup(totalLength, bufferLength, true);
Memory<int> slice = group.GetBoundedSlice(start, length);
Assert.Equal(length, slice.Length);
int expected = (int)start + 1;
foreach (int val in slice.Span)
{
Assert.Equal(expected, val);
expected++;
}
}
public static TheoryData<long, int, long, int> GetBoundedSlice_ErrorData = new TheoryData<long, int, long, int>()
{
{ 300, 100, 110, 91 },
{ 42, 7, 0, 8 },
{ 42, 7, 1, 7 },
{ 42, 7, 1, 30 },
};
[Theory]
[MemberData(nameof(GetBoundedSlice_ErrorData))]
public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length)
{
using MemoryGroup<int> group = this.CreateTestGroup(totalLength, bufferLength, true);
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => group.GetBoundedSlice(start, length));
}
[Fact]
public void Fill()
{
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
foreach (Memory<int> memory in group)
{
Assert.True(memory.Span.SequenceEqual(expectedRow));
}
}
[Fact]
public void Clear()
{
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Clear();
var expectedRow = new int[10];
foreach (Memory<int> memory in group)
{
Assert.True(memory.Span.SequenceEqual(expectedRow));
}
}
private static void MultiplyAllBy2(ReadOnlySpan<int> source, Span<int> target)
{
Assert.Equal(source.Length, target.Length);
for (int k = 0; k < source.Length; k++)
{
target[k] = source[k] * 2;
}
}
[StructLayout(LayoutKind.Sequential, Size = 5)]
private struct S5
{
public override string ToString() => "S5";
}
[StructLayout(LayoutKind.Sequential, Size = 4)]
private struct S4
{
public override string ToString() => "S4";
}
}
}

35
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
{
public abstract class MemoryGroupTestsBase
{
internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator();
/// <summary>
/// Create a group, either uninitialized or filled with incrementing numbers starting with 1.
/// </summary>
internal MemoryGroup<int> CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false)
{
this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int);
var g = MemoryGroup<int>.Allocate(this.MemoryAllocator, totalLength, bufferLength);
if (!fillSequence)
{
return g;
}
int j = 1;
for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1)
{
g.SetElementAt(i, j);
j++;
}
return g;
}
}
}

159
tests/ImageSharp.Tests/Memory/MemorySourceTests.cs

@ -1,159 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
public class MemorySourceTests
{
public class Construction
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void InitializeAsOwner(bool isInternalMemorySource)
{
var data = new Rgba32[21];
var mmg = new TestMemoryManager<Rgba32>(data);
var a = new MemorySource<Rgba32>(mmg, isInternalMemorySource);
Assert.Equal(mmg, a.MemoryOwner);
Assert.Equal(mmg.Memory, a.Memory);
Assert.Equal(isInternalMemorySource, a.HasSwappableContents);
}
[Fact]
public void InitializeAsObserver_MemoryOwner_IsNull()
{
var data = new Rgba32[21];
var mmg = new TestMemoryManager<Rgba32>(data);
var a = new MemorySource<Rgba32>(mmg.Memory);
Assert.Null(a.MemoryOwner);
Assert.Equal(mmg.Memory, a.Memory);
Assert.False(a.HasSwappableContents);
}
}
public class Dispose
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WhenOwnershipIsTransferred_ShouldDisposeMemoryOwner(bool isInternalMemorySource)
{
var mmg = new TestMemoryManager<int>(new int[10]);
var bmg = new MemorySource<int>(mmg, isInternalMemorySource);
bmg.Dispose();
Assert.True(mmg.IsDisposed);
}
[Fact]
public void WhenMemoryObserver_ShouldNotDisposeAnything()
{
var mmg = new TestMemoryManager<int>(new int[10]);
var bmg = new MemorySource<int>(mmg.Memory);
bmg.Dispose();
Assert.False(mmg.IsDisposed);
}
}
public class SwapOrCopyContent
{
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
private MemorySource<T> AllocateMemorySource<T>(int length, AllocationOptions options = AllocationOptions.None)
where T : struct
{
IMemoryOwner<T> owner = this.MemoryAllocator.Allocate<T>(length, options);
return new MemorySource<T>(owner, true);
}
[Fact]
public void WhenBothAreMemoryOwners_ShouldSwap()
{
MemorySource<int> a = this.AllocateMemorySource<int>(13);
MemorySource<int> b = this.AllocateMemorySource<int>(17);
IMemoryOwner<int> aa = a.MemoryOwner;
IMemoryOwner<int> bb = b.MemoryOwner;
Memory<int> aaa = a.Memory;
Memory<int> bbb = b.Memory;
MemorySource<int>.SwapOrCopyContent(ref a, ref b);
Assert.Equal(bb, a.MemoryOwner);
Assert.Equal(aa, b.MemoryOwner);
Assert.Equal(bbb, a.Memory);
Assert.Equal(aaa, b.Memory);
Assert.NotEqual(a.Memory, b.Memory);
}
[Theory]
[InlineData(false, false)]
[InlineData(true, true)]
[InlineData(true, false)]
public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource)
{
var data = new Rgba32[21];
var color = new Rgba32(1, 2, 3, 4);
var destOwner = new TestMemoryManager<Rgba32>(data);
var dest = new MemorySource<Rgba32>(destOwner.Memory);
IMemoryOwner<Rgba32> sourceOwner = this.MemoryAllocator.Allocate<Rgba32>(21);
MemorySource<Rgba32> source = sourceIsOwner
? new MemorySource<Rgba32>(sourceOwner, isInternalMemorySource)
: new MemorySource<Rgba32>(sourceOwner.Memory);
sourceOwner.Memory.Span[10] = color;
// Act:
MemorySource<Rgba32>.SwapOrCopyContent(ref dest, ref source);
// Assert:
Assert.Equal(color, dest.Memory.Span[10]);
Assert.NotEqual(sourceOwner, dest.MemoryOwner);
Assert.NotEqual(destOwner, source.MemoryOwner);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner)
{
var data = new Rgba32[21];
var color = new Rgba32(1, 2, 3, 4);
var destOwner = new TestMemoryManager<Rgba32>(data);
var dest = new MemorySource<Rgba32>(destOwner.Memory);
IMemoryOwner<Rgba32> sourceOwner = this.MemoryAllocator.Allocate<Rgba32>(22);
MemorySource<Rgba32> source = sourceIsOwner
? new MemorySource<Rgba32>(sourceOwner, true)
: new MemorySource<Rgba32>(sourceOwner.Memory);
sourceOwner.Memory.Span[10] = color;
// Act:
Assert.ThrowsAny<InvalidOperationException>(() => MemorySource<Rgba32>.SwapOrCopyContent(ref dest, ref source));
Assert.Equal(color, source.Memory.Span[10]);
Assert.NotEqual(color, dest.Memory.Span[10]);
}
}
}
}

8
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -196,5 +196,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
.Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value))
.Dispose();
}
[Theory]
[WithTestPatternImages(100, 300, PixelTypes.Bgr24)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(41, c => c.BokehBlur());
}
}
}

11
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -103,5 +103,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
image.CompareToReferenceOutput(ValidatorComparer, provider);
}
}
[Theory]
[WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider, EdgeDetectionOperators detector)
where TPixel : struct, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.DetectEdges(detector),
detector);
}
}
}

23
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
};
public static readonly TheoryData<IDither, string> OrderedDitherers
= new TheoryData<IDither,string>
= new TheoryData<IDither, string>
{
{ KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) },
{ KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) },
@ -152,5 +152,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
comparer: ValidatorComparer,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(OrderedDither.Ordered3x3))]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(ErrorDither.FloydSteinberg))]
public void CommonDitherers_WorkWithDiscoBuffers<TPixel>(
TestImageProvider<TPixel> provider,
string name)
where TPixel : struct, IPixel<TPixel>
{
IDither dither = TestUtils.GetDither(name);
if (SkipAllDitherTests)
{
return;
}
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.Dither(dither),
name,
ImageComparer.TolerantPercentage(0.001f));
}
}
}

10
tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs

@ -37,6 +37,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer);
}
[Theory]
[WithTestPatternImages(70, 120, PixelTypes.Rgba32)]
public void FilterProcessor_WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
ColorMatrix m = CreateCombinedTestFilterMatrix();
provider.RunBufferCapacityLimitProcessorTest(37, c => c.Filter(m));
}
private static ColorMatrix CreateCombinedTestFilterMatrix()
{
ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F);

8
tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs

@ -54,6 +54,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays
provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply);
}
[Theory]
[WithTestPatternImages(70, 120, PixelTypes.Rgba32)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
provider.RunBufferCapacityLimitProcessorTest(37, c => this.Apply(c, Color.DarkRed));
}
protected abstract void Apply(IImageProcessingContext ctx, Color color);
protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY);

13
tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs → tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

@ -213,6 +213,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
}
}
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)]
public void WorksWithDiscoBuffers<TPixel>(TestImageProvider<TPixel> provider, int bufferCapacityInPixelRows)
where TPixel : struct, IPixel<TPixel>
{
AffineTransformBuilder builder = new AffineTransformBuilder()
.AppendRotationDegrees(50)
.AppendScale(new SizeF(.6F, .6F));
provider.RunBufferCapacityLimitProcessorTest(
bufferCapacityInPixelRows,
c => c.Transform(builder));
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save