diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index b0efdb60db..d7af873bed 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -303,6 +303,82 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => WrapMemory(Configuration.Default, byteMemory, width, height); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// 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. + /// + /// The pixel type + /// The + /// The that is being transferred to the image + /// 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, + IMemoryOwner byteMemoryOwner, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + + var pixelMemoryOwner = new ByteMemoryOwner(byteMemoryOwner); + + Guard.IsTrue(pixelMemoryOwner.Memory.Length >= width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); + + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, metadata); + } + + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// 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. + /// + /// The pixel type. + /// The + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner byteMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, byteMemoryOwner, width, height, new ImageMetadata()); + + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// 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. + /// + /// The pixel type + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + IMemoryOwner byteMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, byteMemoryOwner, width, height); + /// /// /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as diff --git a/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs b/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs new file mode 100644 index 0000000000..bcf8eabf12 --- /dev/null +++ b/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs @@ -0,0 +1,54 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; + +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 ByteMemoryOwner : IMemoryOwner + where T : unmanaged + { + private readonly IMemoryOwner memoryOwner; + private readonly ByteMemoryManager memoryManager; + private bool disposedValue; + + /// + /// Initializes a new instance of the class. + /// + /// The of instance to wrap. + public ByteMemoryOwner(IMemoryOwner memoryOwner) + { + this.memoryOwner = memoryOwner; + this.memoryManager = new ByteMemoryManager(memoryOwner.Memory); + } + + /// + public Memory Memory => this.memoryManager.Memory; + + private void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + this.memoryOwner.Dispose(); + } + + this.disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 17c73cc834..1b40f43ab1 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -413,6 +413,32 @@ namespace SixLabors.ImageSharp.Tests Image.WrapMemory(memory, height, width); } + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(1023, 32, 32)] + public void WrapMemory_IMemoryOwnerOfByte_InvalidSize(int size, int height, int width) + { + var array = new byte[size * Unsafe.SizeOf()]; + var memory = new TestMemoryOwner { Memory = array }; + + Assert.Throws(() => Image.WrapMemory(memory, height, width)); + } + + [Theory] + [InlineData(25, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1024, 32, 32)] + [InlineData(2048, 32, 32)] + public void WrapMemory_IMemoryOwnerOfByte_ValidSize(int size, int height, int width) + { + var array = new byte[size * Unsafe.SizeOf()]; + var memory = new TestMemoryOwner { Memory = array }; + + Image.WrapMemory(memory, height, width); + } + [Theory] [InlineData(0, 5, 5)] [InlineData(20, 5, 5)]