Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size
this.Component = component;

this.BlockAreaSize = component.SubSamplingDivisors * blockSize;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
this.ColorBuffer = memoryAllocator.Allocate2DOverAligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.BlockAreaSize.Height);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public ComponentProcessor(MemoryAllocator memoryAllocator, Component component,
this.blockAreaSize = component.SubSamplingDivisors * 8;

// alignment of 8 so each block stride can be sampled from a single 'ref pointer'
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
this.ColorBuffer = memoryAllocator.Allocate2DOverAligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
8,
Expand Down
25 changes: 24 additions & 1 deletion src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public abstract class MemoryAllocator
/// </summary>
public static MemoryAllocator Default { get; } = Create();

/// <summary>
/// Gets or sets the maximum allowable allocatable size of a 2 dimensional buffer.
/// Defaults to <value>65535 * 65535.</value>
/// </summary>
public Size MaxAllocatableSize2D { get; set; } = new Size(ushort.MaxValue, ushort.MaxValue);

/// <summary>
/// Gets or sets the maximum allowable allocatable size of a 1 dimensional buffer.
/// </summary>
/// Defaults to <value>1073741823.</value>
public int MaxAllocatableSize1D { get; set; } = int.MaxValue / 2;

/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
/// </summary>
Expand All @@ -42,7 +54,7 @@ public static MemoryAllocator Create(MemoryAllocatorOptions options) =>
new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes);

/// <summary>
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
/// Allocates an <see cref="IMemoryOwner{T}"/>, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer.</typeparam>
/// <param name="length">Size of the buffer to allocate.</param>
Expand All @@ -64,6 +76,7 @@ public virtual void ReleaseRetainedResources()
/// <summary>
/// Allocates a <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer.</typeparam>
/// <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>
Expand All @@ -75,4 +88,14 @@ internal virtual MemoryGroup<T> AllocateGroup<T>(
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(this, totalLength, bufferAlignment, options);

internal static void MemoryGuardMustBeBetweenOrEqualTo(int value, int min, int max, string paramName)
{
if (value >= min && value <= max)
{
return;
}

throw new InvalidMemoryOperationException($"Parameter \"{paramName}\" must be between or equal to {min} and {max}, was {value}");
}
}
6 changes: 3 additions & 3 deletions src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Buffers;
Expand All @@ -7,7 +7,7 @@
namespace SixLabors.ImageSharp.Memory;

/// <summary>
/// Implements <see cref="MemoryAllocator"/> by newing up managed arrays on every allocation request.
/// Implements <see cref="MemoryAllocator"/> by creating new managed arrays on every allocation request.
/// </summary>
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
Expand All @@ -17,7 +17,7 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
MemoryGuardMustBeBetweenOrEqualTo(length, 0, this.MaxAllocatableSize1D, nameof(length));

return new BasicArrayBuffer<T>(new T[length]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,14 @@ internal UniformUnmanagedMemoryPoolMemoryAllocator(
protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes;

/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(
int length,
AllocationOptions options = AllocationOptions.None)
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
MemoryGuardMustBeBetweenOrEqualTo(length, 0, this.MaxAllocatableSize1D, nameof(length));
int lengthInBytes = length * Unsafe.SizeOf<T>();

if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>(length);
SharedArrayPoolBuffer<T> buffer = new(length);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
Expand All @@ -102,8 +100,7 @@ public override IMemoryOwner<T> Allocate<T>(
UnmanagedMemoryHandle mem = this.pool.Rent();
if (mem.IsValid)
{
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, length, options.Has(AllocationOptions.Clean));
return buffer;
return this.pool.CreateGuardedBuffer<T>(mem, length, options.Has(AllocationOptions.Clean));
}
}

Expand All @@ -124,7 +121,7 @@ internal override MemoryGroup<T> AllocateGroup<T>(

if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);
SharedArrayPoolBuffer<T> buffer = new((int)totalLength);
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
}

Expand Down
4 changes: 3 additions & 1 deletion src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ internal class UnmanagedMemoryAllocator : MemoryAllocator

public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
var buffer = UnmanagedBuffer<T>.Allocate(length);
MemoryGuardMustBeBetweenOrEqualTo(length, 0, this.MaxAllocatableSize1D, nameof(length));

UnmanagedBuffer<T> buffer = UnmanagedBuffer<T>.Allocate(length);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
Expand Down
22 changes: 15 additions & 7 deletions src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@ public static class MemoryAllocatorExtensions
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The buffer width.</param>
/// <param name="height">The buffer height.</param>
/// <param name="preferContiguosImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="preferContiguousImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
bool preferContiguosImageBuffers,
bool preferContiguousImageBuffers,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
MemoryAllocator.MemoryGuardMustBeBetweenOrEqualTo(width, 0, memoryAllocator.MaxAllocatableSize2D.Width, nameof(width));
MemoryAllocator.MemoryGuardMustBeBetweenOrEqualTo(height, 0, memoryAllocator.MaxAllocatableSize2D.Height, nameof(height));

long groupLength = (long)width * height;
MemoryGroup<T> memoryGroup;
if (preferContiguosImageBuffers && groupLength < int.MaxValue)
if (preferContiguousImageBuffers && groupLength < int.MaxValue)
{
IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>((int)groupLength, options);
memoryGroup = MemoryGroup<T>.CreateContiguous(buffer, false);
Expand Down Expand Up @@ -69,16 +72,16 @@ public static Buffer2D<T> Allocate2D<T>(
/// <typeparam name="T">The type of buffer items to allocate.</typeparam>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="size">The buffer size.</param>
/// <param name="preferContiguosImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="preferContiguousImageBuffers">A value indicating whether the allocated buffer should be contiguous, unless bigger than <see cref="int.MaxValue"/>.</param>
/// <param name="options">The allocation options.</param>
/// <returns>The <see cref="Buffer2D{T}"/>.</returns>
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
bool preferContiguosImageBuffers,
bool preferContiguousImageBuffers,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options);
Allocate2D<T>(memoryAllocator, size.Width, size.Height, preferContiguousImageBuffers, options);

/// <summary>
/// Allocates a buffer of value type objects interpreted as a 2D region
Expand All @@ -96,14 +99,17 @@ public static Buffer2D<T> Allocate2D<T>(
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, false, options);

internal static Buffer2D<T> Allocate2DOveraligned<T>(
internal static Buffer2D<T> Allocate2DOverAligned<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
int alignmentMultiplier,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
MemoryAllocator.MemoryGuardMustBeBetweenOrEqualTo(width, 0, memoryAllocator.MaxAllocatableSize2D.Width, nameof(width));
MemoryAllocator.MemoryGuardMustBeBetweenOrEqualTo(height, 0, memoryAllocator.MaxAllocatableSize2D.Height, nameof(height));

long groupLength = (long)width * height;
MemoryGroup<T> memoryGroup = memoryAllocator.AllocateGroup<T>(
groupLength,
Expand All @@ -127,6 +133,8 @@ internal static IMemoryOwner<byte> AllocatePaddedPixelRowBuffer(
int paddingInBytes)
{
int length = (width * pixelSizeInBytes) + paddingInBytes;
MemoryAllocator.MemoryGuardMustBeBetweenOrEqualTo(length, 0, memoryAllocator.MaxAllocatableSize1D, nameof(length));

return memoryAllocator.Allocate<byte>(length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private ResizeKernelMap(
this.sourceLength = sourceLength;
this.DestinationLength = destinationLength;
this.MaxDiameter = (radius * 2) + 1;
this.data = memoryAllocator.Allocate2D<float>(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean);
this.data = memoryAllocator.Allocate2D<float>(this.MaxDiameter, bufferHeight, preferContiguousImageBuffers: true, AllocationOptions.Clean);
this.pinHandle = this.data.DangerousGetSingleMemory().Pin();
this.kernels = new ResizeKernel[destinationLength];
this.tempValues = new double[this.MaxDiameter];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ResizeWorker(
this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D<Vector4>(
this.workerHeight,
targetWorkingRect.Width,
preferContiguosImageBuffers: true,
preferContiguousImageBuffers: true,
options: AllocationOptions.Clean);

this.tempRowBuffer = configuration.MemoryAllocator.Allocate<Vector4>(this.sourceRectangle.Width);
Expand Down
9 changes: 9 additions & 0 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,4 +558,13 @@ public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel
// Compare to reference output instead.
image.CompareToReferenceOutput(provider, extension: "png");
}

[Theory]
[WithFile(Issue2696, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsException_Issue2696<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
=> Assert.Throws<InvalidImageContentException>(() =>
{
using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
});
}
9 changes: 6 additions & 3 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
Expand Down Expand Up @@ -349,13 +350,15 @@ public void Encode_PreservesColorProfile<TPixel>(TestImageProvider<TPixel> provi
}

[Theory]
[InlineData(1, 66535)]
[InlineData(66535, 1)]
[InlineData(1, 65536)]
[InlineData(65536, 1)]
public void Encode_WorksWithSizeGreaterThen65k(int width, int height)
{
Exception exception = Record.Exception(() =>
{
using Image image = new Image<Rgba32>(width, height);
Configuration c = Configuration.CreateDefaultInstance();
c.MemoryAllocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null) { MaxAllocatableSize2D = new(65537, 65537) };
using Image image = new Image<Rgba32>(c, width, height);
using MemoryStream memStream = new();
image.Save(memStream, BmpEncoder);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ public BufferTests()

[Theory]
[InlineData(-1)]
[InlineData(1073741823 + 1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<BigStruct>(length));
Assert.Equal("length", ex.ParamName);
}
=> Assert.Throws<InvalidMemoryOperationException>(() => this.MemoryAllocator.Allocate<BigStruct>(length));

[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ public void AllocateGroup_SizeInBytesOverLongMaxValue_ThrowsInvalidMemoryOperati
Assert.Throws<InvalidMemoryOperationException>(() => allocator.AllocateGroup<S4>(int.MaxValue * (long)int.MaxValue, int.MaxValue));
}

[Theory]
[InlineData(-1)]
[InlineData(1073741823 + 1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
=> Assert.Throws<InvalidMemoryOperationException>(() => new UniformUnmanagedMemoryPoolMemoryAllocator(null).Allocate<byte>(length));

[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
{
Expand Down
24 changes: 21 additions & 3 deletions tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardless
using Buffer2D<byte> buffer = useSizeOverload ?
this.MemoryAllocator.Allocate2D<byte>(
new Size(200, 200),
preferContiguosImageBuffers: true) :
preferContiguousImageBuffers: true) :
this.MemoryAllocator.Allocate2D<byte>(
200,
200,
preferContiguosImageBuffers: true);
preferContiguousImageBuffers: true);
Assert.Equal(1, buffer.FastMemoryGroup.Count);
Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength);
}
Expand All @@ -87,7 +87,7 @@ public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int
{
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;

using Buffer2D<int> buffer = this.MemoryAllocator.Allocate2DOveraligned<int>(width, height, alignmentMultiplier);
using Buffer2D<int> buffer = this.MemoryAllocator.Allocate2DOverAligned<int>(width, height, alignmentMultiplier);
MemoryGroup<int> memoryGroup = buffer.FastMemoryGroup;
int expectedAlignment = width * alignmentMultiplier;

Expand Down Expand Up @@ -337,4 +337,22 @@ public void PublicMemoryGroup_IsMemoryGroupView()
Assert.False(mgBefore.IsValid);
Assert.NotSame(mgBefore, buffer1.MemoryGroup);
}

[Theory]
[InlineData(-1)]
[InlineData(ushort.MaxValue + 1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
=> Assert.Throws<InvalidMemoryOperationException>(() => this.MemoryAllocator.Allocate2D<byte>(length, length));

[Theory]
[InlineData(-1)]
[InlineData(ushort.MaxValue + 1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException_Size(int length)
=> Assert.Throws<InvalidMemoryOperationException>(() => this.MemoryAllocator.Allocate2D<byte>(new Size(length, length)));

[Theory]
[InlineData(-1)]
[InlineData(ushort.MaxValue + 1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException_OverAligned(int length)
=> Assert.Throws<InvalidMemoryOperationException>(() => this.MemoryAllocator.Allocate2DOverAligned<byte>(length, length, 1));
}
2 changes: 2 additions & 0 deletions tests/ImageSharp.Tests/TestImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ public static class Bmp

public const string BlackWhitePalletDataMatrix = "Bmp/bit1datamatrix.bmp";

public const string Issue2696 = "Bmp/issue-2696.bmp";

public static readonly string[] BitFields =
{
Rgb32bfdef,
Expand Down
3 changes: 3 additions & 0 deletions tests/Images/Input/Bmp/issue-2696.bmp
Git LFS file not shown