diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 0ac4c29eae..3d9552e423 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -94,6 +94,14 @@ namespace SixLabors.ImageSharp
}
}
+ ///
+ /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible.
+ ///
+ ///
+ /// Contiguous allocations are not possible, if the image needs a buffer larger than .
+ ///
+ public bool PreferContiguousImageBuffers { get; set; }
+
///
/// Gets a set of properties for the Configuration.
///
diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs
index b29608f62a..949b8999a5 100644
--- a/src/ImageSharp/ImageFrame{TPixel}.cs
+++ b/src/ImageSharp/ImageFrame{TPixel}.cs
@@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean);
+ this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(
+ width,
+ height,
+ configuration.PreferContiguousImageBuffers,
+ AllocationOptions.Clean);
}
///
@@ -87,7 +91,10 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height);
+ this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(
+ width,
+ height,
+ configuration.PreferContiguousImageBuffers);
this.Clear(backgroundColor);
}
@@ -131,7 +138,10 @@ namespace SixLabors.ImageSharp
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(source, nameof(source));
- this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height);
+ this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(
+ source.PixelBuffer.Width,
+ source.PixelBuffer.Height,
+ configuration.PreferContiguousImageBuffers);
source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup);
}
diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
index 9cf1af659f..abcf078ac7 100644
--- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
+++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
@@ -20,20 +20,68 @@ namespace SixLabors.ImageSharp.Memory
/// The memory allocator.
/// The buffer width.
/// The buffer height.
+ /// A value indicating whether the allocated buffer should be contiguous, unless bigger than .
/// The allocation options.
/// The .
public static Buffer2D Allocate2D(
this MemoryAllocator memoryAllocator,
int width,
int height,
+ bool preferContiguosImageBuffers,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
long groupLength = (long)width * height;
- MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options);
+ MemoryGroup memoryGroup;
+ if (preferContiguosImageBuffers && groupLength < int.MaxValue)
+ {
+ IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options);
+ memoryGroup = MemoryGroup.CreateContiguous(buffer, false);
+ }
+ else
+ {
+ memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options);
+ }
+
return new Buffer2D(memoryGroup, width, height);
}
+ ///
+ /// Allocates a buffer of value type objects interpreted as a 2D region
+ /// of x elements.
+ ///
+ /// The type of buffer items to allocate.
+ /// The memory allocator.
+ /// The buffer width.
+ /// The buffer height.
+ /// The allocation options.
+ /// The .
+ public static Buffer2D Allocate2D(
+ this MemoryAllocator memoryAllocator,
+ int width,
+ int height,
+ AllocationOptions options = AllocationOptions.None)
+ where T : struct =>
+ Allocate2D(memoryAllocator, width, height, false, options);
+
+ ///
+ /// Allocates a buffer of value type objects interpreted as a 2D region
+ /// of width x height elements.
+ ///
+ /// The type of buffer items to allocate.
+ /// The memory allocator.
+ /// The buffer size.
+ /// A value indicating whether the allocated buffer should be contiguous, unless bigger than .
+ /// The allocation options.
+ /// The .
+ public static Buffer2D Allocate2D(
+ this MemoryAllocator memoryAllocator,
+ Size size,
+ bool preferContiguosImageBuffers,
+ AllocationOptions options = AllocationOptions.None)
+ where T : struct =>
+ Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options);
+
///
/// Allocates a buffer of value type objects interpreted as a 2D region
/// of width x height elements.
@@ -48,7 +96,7 @@ namespace SixLabors.ImageSharp.Memory
Size size,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
- Allocate2D(memoryAllocator, size.Width, size.Height, options);
+ Allocate2D(memoryAllocator, size.Width, size.Height, false, options);
internal static Buffer2D Allocate2DOveraligned(
this MemoryAllocator memoryAllocator,
diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
index dd8275ee8e..50b21ba8d6 100644
--- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
+++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
@@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
-
+using SixLabors.ImageSharp.Tests.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests
@@ -339,6 +340,26 @@ namespace SixLabors.ImageSharp.Tests
Assert.False(this.Image.Frames.Contains(frame));
}
+ [Fact]
+ public void PreferContiguousImageBuffers_True_AppliedToAllFrames()
+ {
+ Configuration configuration = Configuration.Default.Clone();
+ configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 };
+ configuration.PreferContiguousImageBuffers = true;
+
+ using var image = new Image(configuration, 100, 100);
+ image.Frames.CreateFrame();
+ image.Frames.InsertFrame(0, image.Frames[0]);
+ image.Frames.CreateFrame(Color.Red);
+
+ Assert.Equal(4, image.Frames.Count);
+ IEnumerable> frames = image.Frames;
+ foreach (ImageFrame frame in frames)
+ {
+ Assert.True(frame.TryGetSinglePixelSpan(out Span span));
+ }
+ }
+
[Fact]
public void DisposeCall_NoThrowIfCalledMultiple()
{
diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
index 09ed2afa52..f6656ec5fc 100644
--- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
+++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
@@ -22,17 +22,17 @@ namespace SixLabors.ImageSharp.Tests
}
[Fact]
- public unsafe void Set_MaximumPoolSizeMegabytes_CreateImage_MaximumPoolSizeMegabytes()
+ public void PreferContiguousImageBuffers_CreateImage_MaximumPoolSizeMegabytes()
{
+ // Run remotely to avoid large allocation in the test process:
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()
{
- Configuration.Default.MemoryAllocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions()
- {
- MinimumContiguousBlockSizeBytes = sizeof(Rgba32) * 8192 * 4096
- });
- using Image image = new Image(8192, 4096);
+ Configuration configuration = Configuration.Default.Clone();
+ configuration.PreferContiguousImageBuffers = true;
+
+ using var image = new Image(configuration, 8192, 4096);
Assert.True(image.TryGetSinglePixelSpan(out Span span));
Assert.Equal(8192 * 4096, span.Length);
}
diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
index 845ea9ca40..04abc6585e 100644
--- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
+++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
@@ -68,6 +68,25 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
}
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload)
+ {
+ this.MemoryAllocator.BufferCapacityInBytes = 10_000;
+
+ using Buffer2D buffer = useSizeOverload ?
+ this.MemoryAllocator.Allocate2D(
+ new Size(200, 200),
+ preferContiguosImageBuffers: true) :
+ this.MemoryAllocator.Allocate2D(
+ 200,
+ 200,
+ preferContiguosImageBuffers: true);
+ Assert.Equal(1, buffer.FastMemoryGroup.Count);
+ Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength);
+ }
+
[Theory]
[InlineData(50, 10, 20, 4)]
public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier)