diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs
index 2d3c29ed42..d89c44dc56 100644
--- a/src/ImageSharp/Image.WrapMemory.cs
+++ b/src/ImageSharp/Image.WrapMemory.cs
@@ -17,7 +17,7 @@ public abstract partial class Image
{
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
///
/// The pixel type
/// The
@@ -38,6 +38,7 @@ public static Image WrapMemory(
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
+ Guard.IsTrue(pixelMemory.Length == width * height, nameof(pixelMemory), "The length of the input memory doesn't match the specified image size");
var memorySource = MemoryGroup.Wrap(pixelMemory);
return new Image(configuration, memorySource, width, height, metadata);
@@ -45,7 +46,7 @@ public static Image WrapMemory(
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
///
/// The pixel type
/// The
@@ -64,7 +65,7 @@ public static Image WrapMemory(
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
///
/// The pixel type.
@@ -81,7 +82,7 @@ public static Image WrapMemory(
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
/// The ownership of the is being transferred to the new instance,
/// meaning that the caller is not allowed to dispose .
/// It will be disposed together with the result image.
@@ -105,6 +106,7 @@ public static Image WrapMemory(
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
+ Guard.IsTrue(pixelMemoryOwner.Memory.Length == width * height, nameof(pixelMemoryOwner), "The length of the input memory doesn't match the specified image size");
var memorySource = MemoryGroup.Wrap(pixelMemoryOwner);
return new Image(configuration, memorySource, width, height, metadata);
@@ -112,7 +114,7 @@ public static Image WrapMemory(
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
/// The ownership of the is being transferred to the new instance,
/// meaning that the caller is not allowed to dispose .
/// It will be disposed together with the result image.
@@ -134,7 +136,7 @@ public static Image WrapMemory(
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an ImageSharp instance.
+ /// allowing to view/manipulate it as an instance.
/// The ownership of the is being transferred to the new instance,
/// meaning that the caller is not allowed to dispose .
/// It will be disposed together with the result image.
@@ -150,5 +152,73 @@ public static Image WrapMemory(
int height)
where TPixel : unmanaged, IPixel
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
+
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
+ /// allowing to view/manipulate it as an instance.
+ ///
+ /// The pixel type
+ /// The
+ /// The byte memory representing the pixel data.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// The .
+ /// The configuration is null.
+ /// The metadata is null.
+ /// An instance
+ public static Image WrapMemory(
+ Configuration configuration,
+ Memory byteMemory,
+ int width,
+ int height,
+ ImageMetadata metadata)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(metadata, nameof(metadata));
+
+ var memoryManager = new ByteMemoryManager(byteMemory);
+
+ Guard.IsTrue(memoryManager.Memory.Length == width * height, nameof(byteMemory), "The length of the input memory doesn't match the specified image size");
+
+ var memorySource = MemoryGroup.Wrap(memoryManager.Memory);
+ return new Image(configuration, memorySource, width, height, metadata);
+ }
+
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
+ /// allowing to view/manipulate it as an instance.
+ ///
+ /// The pixel type
+ /// The
+ /// The byte memory representing the pixel data.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// The configuration is null.
+ /// An instance.
+ public static Image WrapMemory(
+ Configuration configuration,
+ Memory byteMemory,
+ int width,
+ int height)
+ where TPixel : unmanaged, IPixel
+ => WrapMemory(configuration, byteMemory, width, height, new ImageMetadata());
+
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
+ /// allowing to view/manipulate it as an instance.
+ /// The memory is being observed, the caller remains responsible for managing it's lifecycle.
+ ///
+ /// The pixel type.
+ /// The byte memory representing the pixel data.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// An instance.
+ public static Image WrapMemory(
+ Memory byteMemory,
+ int width,
+ int height)
+ where TPixel : unmanaged, IPixel
+ => WrapMemory(Configuration.Default, byteMemory, width, height);
}
}
diff --git a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs
new file mode 100644
index 0000000000..223709df65
--- /dev/null
+++ b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// A custom that can wrap of instances
+ /// and cast them to be for any arbitrary unmanaged value type.
+ ///
+ /// The value type to use when casting the wrapped instance.
+ internal sealed class ByteMemoryManager : MemoryManager
+ where T : unmanaged
+ {
+ ///
+ /// The wrapped of instance.
+ ///
+ private readonly Memory memory;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The of instance to wrap.
+ public ByteMemoryManager(Memory memory)
+ {
+ this.memory = memory;
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ ///
+ public override Span GetSpan()
+ {
+ return MemoryMarshal.Cast(this.memory.Span);
+ }
+
+ ///
+ public override MemoryHandle Pin(int elementIndex = 0)
+ {
+ // We need to adjust the offset into the wrapped byte segment,
+ // as the input index refers to the target-cast memory of T.
+ // We just have to shift this index by the byte size of T.
+ return this.memory.Slice(elementIndex * Unsafe.SizeOf()).Pin();
+ }
+
+ ///
+ public override void Unpin()
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs
index aa475a80f1..c2551ccf2c 100644
--- a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs
+++ b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs
@@ -13,13 +13,27 @@ namespace SixLabors.ImageSharp.Memory
///
internal static class MemoryOwnerExtensions
{
+ ///
+ /// Gets a from an instance.
+ ///
+ /// The buffer
+ /// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span GetSpan(this IMemoryOwner buffer)
- => buffer.Memory.Span;
+ {
+ return buffer.Memory.Span;
+ }
+ ///
+ /// Gets the length of an internal buffer.
+ ///
+ /// The buffer
+ /// The length of the buffer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length(this IMemoryOwner buffer)
- => buffer.GetSpan().Length;
+ {
+ return buffer.Memory.Length;
+ }
///
/// Gets a to an offsetted position inside the buffer.
@@ -56,8 +70,16 @@ public static void Clear(this IMemoryOwner buffer)
buffer.GetSpan().Clear();
}
+ ///
+ /// Gets a reference to the first item in the internal buffer for an instance.
+ ///
+ /// The buffer
+ /// A reference to the first item within the memory wrapped by
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference(this IMemoryOwner buffer)
- where T : struct =>
- ref MemoryMarshal.GetReference(buffer.GetSpan());
+ where T : struct
+ {
+ return ref MemoryMarshal.GetReference(buffer.GetSpan());
+ }
}
}
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
index b11411e32a..448eb3833b 100644
--- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
+++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
@@ -262,7 +262,7 @@ public PaletteDitherRowOperation(
this.source = source;
this.bounds = bounds;
this.scale = processor.DitherScale;
- this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length);
+ this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
index 2b30d9459f..7dc7dbb30c 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
@@ -6,10 +6,10 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@@ -80,10 +80,52 @@ public override void Unpin()
}
}
+ public sealed class CastMemoryManager : MemoryManager
+ where TFrom : unmanaged
+ where TTo : unmanaged
+ {
+ private readonly Memory memory;
+
+ public CastMemoryManager(Memory memory)
+ {
+ this.memory = memory;
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ ///
+ public override Span GetSpan()
+ {
+ return MemoryMarshal.Cast(this.memory.Span);
+ }
+
+ ///
+ public override MemoryHandle Pin(int elementIndex = 0)
+ {
+ int byteOffset = elementIndex * Unsafe.SizeOf();
+ int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf(), out int remainder);
+
+ if (remainder != 0)
+ {
+ ThrowHelper.ThrowArgumentException("The input index doesn't result in an aligned item access", nameof(elementIndex));
+ }
+
+ return this.memory.Slice(shiftedOffset).Pin();
+ }
+
+ ///
+ public override void Unpin()
+ {
+ }
+ }
+
[Fact]
public void WrapMemory_CreatedImageIsCorrect()
{
- Configuration cfg = Configuration.Default.Clone();
+ var cfg = Configuration.CreateDefaultInstance();
var metaData = new ImageMetadata();
var array = new Rgba32[25];
@@ -173,6 +215,124 @@ public void WrapSystemDrawingBitmap_WhenOwned()
}
}
+ [Fact]
+ public void WrapMemory_FromBytes_CreatedImageIsCorrect()
+ {
+ var cfg = Configuration.CreateDefaultInstance();
+ var metaData = new ImageMetadata();
+
+ var array = new byte[25 * Unsafe.SizeOf()];
+ var memory = new Memory(array);
+
+ using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData))
+ {
+ Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan));
+ ref Rgba32 pixel0 = ref imageSpan[0];
+ Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0));
+
+ Assert.Equal(cfg, image.GetConfiguration());
+ Assert.Equal(metaData, image.Metadata);
+ }
+ }
+
+ [Fact]
+ public void WrapSystemDrawingBitmap_FromBytes_WhenObserved()
+ {
+ if (ShouldSkipBitmapTest)
+ {
+ return;
+ }
+
+ using (var bmp = new Bitmap(51, 23))
+ {
+ using (var memoryManager = new BitmapMemoryManager(bmp))
+ {
+ Memory pixelMemory = memoryManager.Memory;
+ Memory byteMemory = new CastMemoryManager(pixelMemory).Memory;
+ Bgra32 bg = Color.Red;
+ Bgra32 fg = Color.Green;
+
+ using (var image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height))
+ {
+ Span pixelSpan = pixelMemory.Span;
+ Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span;
+
+ // We can't compare the two Memory instances directly as they wrap different memory managers.
+ // To check that the underlying data matches, we can just manually check their lenth, and the
+ // fact that a reference to the first pixel in both spans is actually the same memory location.
+ Assert.Equal(pixelSpan.Length, imageSpan.Length);
+ Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));
+
+ Assert.True(image.TryGetSinglePixelSpan(out imageSpan));
+ imageSpan.Fill(bg);
+ for (var i = 10; i < 20; i++)
+ {
+ image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
+ }
+ }
+
+ Assert.False(memoryManager.IsDisposed);
+ }
+
+ string fn = System.IO.Path.Combine(
+ TestEnvironment.ActualOutputDirectoryFullPath,
+ $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp");
+
+ bmp.Save(fn, ImageFormat.Bmp);
+ }
+ }
+
+ [Theory]
+ [InlineData(0, 5, 5)]
+ [InlineData(20, 5, 5)]
+ [InlineData(26, 5, 5)]
+ [InlineData(2, 1, 1)]
+ [InlineData(1023, 32, 32)]
+ public void WrapMemory_MemoryOfT_InvalidSize(int size, int height, int width)
+ {
+ var array = new Rgba32[size];
+ var memory = new Memory(array);
+
+ Assert.Throws(() => Image.WrapMemory(memory, height, width));
+ }
+
+ private class TestMemoryOwner : IMemoryOwner
+ {
+ public Memory Memory { get; set; }
+
+ public void Dispose()
+ {
+ }
+ }
+
+ [Theory]
+ [InlineData(0, 5, 5)]
+ [InlineData(20, 5, 5)]
+ [InlineData(26, 5, 5)]
+ [InlineData(2, 1, 1)]
+ [InlineData(1023, 32, 32)]
+ public void WrapMemory_IMemoryOwnerOfT_InvalidSize(int size, int height, int width)
+ {
+ var array = new Rgba32[size];
+ var memory = new TestMemoryOwner { Memory = array };
+
+ Assert.Throws(() => Image.WrapMemory(memory, height, width));
+ }
+
+ [Theory]
+ [InlineData(0, 5, 5)]
+ [InlineData(20, 5, 5)]
+ [InlineData(26, 5, 5)]
+ [InlineData(2, 1, 1)]
+ [InlineData(1023, 32, 32)]
+ public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width)
+ {
+ var array = new byte[size * Unsafe.SizeOf()];
+ var memory = new Memory(array);
+
+ Assert.Throws(() => Image.WrapMemory(memory, height, width));
+ }
+
private static bool ShouldSkipBitmapTest =>
!TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1");
}