diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index c24a932e1..77432c3ad 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; + using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -41,6 +43,27 @@ namespace SixLabors.ImageSharp /// allowing to view/manipulate it as an ImageSharp instance. /// /// The pixel type + /// The + /// The pixel memory + /// The width of the memory image + /// The height of the memory image + /// An instance + public static Image WrapMemory( + Configuration config, + Memory pixelMemory, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(config, pixelMemory, width, height, new ImageMetaData()); + } + + /// + /// Wraps an existing contigous memory area of 'width'x'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The memory is being observed, the caller remains responsible for managing it's lifecycle. + /// + /// The pixel type /// The pixel memory /// The width of the memory image /// The height of the memory image @@ -51,7 +74,77 @@ namespace SixLabors.ImageSharp int height) where TPixel : struct, IPixel { - return WrapMemory(Configuration.Default, pixelMemory, width, height, new ImageMetaData()); + return WrapMemory(Configuration.Default, pixelMemory, width, height); + } + + /// + /// Wraps an existing contigous memory area of 'width'x'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transfered 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 transfered to the image + /// The width of the memory image + /// The height of the memory image + /// The + /// An instance + public static Image WrapMemory( + Configuration config, + IMemoryOwner pixelMemoryOwner, + int width, + int height, + ImageMetaData metaData) + where TPixel : struct, IPixel + { + var memorySource = new MemorySource(pixelMemoryOwner, false); + return new Image(config, memorySource, width, height, metaData); + } + + /// + /// Wraps an existing contigous memory area of 'width'x'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transfered 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 transfered to the image + /// The width of the memory image + /// The height of the memory image + /// An instance + public static Image WrapMemory( + Configuration config, + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetaData()); + } + + /// + /// Wraps an existing contigous memory area of 'width'x'height' pixels, + /// allowing to view/manipulate it as an ImageSharp instance. + /// The ownership of the is being transfered 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 transfered to the image + /// The width of the memory image + /// The height of the memory image + /// An instance + public static Image WrapMemory( + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : struct, IPixel + { + return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs index 27bca11c1..4253307ef 100644 --- a/src/ImageSharp/Memory/MemorySource.cs +++ b/src/ImageSharp/Memory/MemorySource.cs @@ -8,6 +8,7 @@ namespace SixLabors.Memory { /// /// Holds a that is either OWNED or CONSUMED. + /// When the memory is being owned, the instance is also known. /// Implements content transfer logic in that depends on the ownership status. /// This is needed to transfer the contents of a temporary /// to a persistent without copying the buffer. diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 57f757176..815684d84 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -5,9 +5,11 @@ using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; using SixLabors.Shapes; using SixLabors.ImageSharp.Processing; using Xunit; @@ -19,13 +21,17 @@ namespace SixLabors.ImageSharp.Tests { public class WrapMemory { + /// + /// A exposing the locked pixel memory of a instance. + /// TODO: This should be an example in https://github.com/SixLabors/Samples + /// class BitmapMemoryManager : MemoryManager { - private System.Drawing.Bitmap bitmap; + private readonly Bitmap bitmap; - private BitmapData bmpData; + private readonly BitmapData bmpData; - private int length; + private readonly int length; public BitmapMemoryManager(Bitmap bitmap) { @@ -40,9 +46,21 @@ namespace SixLabors.ImageSharp.Tests this.length = bitmap.Width * bitmap.Height; } + public bool IsDisposed { get; private set; } + protected override void Dispose(bool disposing) { - this.bitmap.UnlockBits(this.bmpData); + if (this.IsDisposed) + { + return; + } + + if (disposing) + { + this.bitmap.UnlockBits(this.bmpData); + } + + this.IsDisposed = true; } public override unsafe Span GetSpan() @@ -63,7 +81,26 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void WrapSystemDrawingBitmap() + public void WrapMemory_CreatedImageIsCorrect() + { + Configuration cfg = Configuration.Default.ShallowCopy(); + var metaData = new ImageMetaData(); + + var array = new Rgba32[25]; + var memory = new Memory(array); + + using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) + { + ref Rgba32 pixel0 = ref image.GetPixelSpan()[0]; + Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); + + Assert.Equal(cfg, image.GetConfiguration()); + Assert.Equal(metaData, image.MetaData); + } + } + + [Fact] + public void WrapSystemDrawingBitmap_WhenObserved() { using (var bmp = new Bitmap(51, 23)) { @@ -75,13 +112,41 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { + Assert.Equal(memory, image.GetPixelMemory()); image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); } + + Assert.False(memoryManager.IsDisposed); } string fn = System.IO.Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, - "WrapSystemDrawingBitmap.bmp"); + $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); + + bmp.Save(fn, ImageFormat.Bmp); + } + } + + [Fact] + public void WrapSystemDrawingBitmap_WhenOwned() + { + using (var bmp = new Bitmap(51, 23)) + { + var memoryManager = new BitmapMemoryManager(bmp); + Bgra32 bg = NamedColors.Red; + Bgra32 fg = NamedColors.Green; + + using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) + { + Assert.Equal(memoryManager.Memory, image.GetPixelMemory()); + image.Mutate(c => c.Fill(bg).Fill(fg, new RectangularPolygon(10, 10, 10, 10))); + } + + Assert.True(memoryManager.IsDisposed); + + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenOwned)}.bmp"); bmp.Save(fn, ImageFormat.Bmp); }