diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e8268d3812..4753e90e0c 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -266,6 +266,18 @@ namespace SixLabors.ImageSharp } } + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 710b4fedf4..d01706b01d 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; @@ -300,6 +301,18 @@ namespace SixLabors.ImageSharp } } + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index 9e3aed2b0a..4d01fd754b 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; @@ -93,6 +95,59 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.Frames.RootFrame.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); + } + else + { + image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); + } + }); + } } public class ProcessPixelRows : ProcessPixelRowsTestBase diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 4f68453a9f..7b67875299 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Memory; @@ -167,6 +168,59 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.CopyPixelDataTo(new byte[199]); + } + else + { + image.CopyPixelDataTo(new La16[99]); + } + }); + } } public class ProcessPixelRows : ProcessPixelRowsTestBase diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index e24fb7624f..3c27b60fe4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -54,6 +54,32 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static byte[] GetRandomBytes(int length, int seed = 42) + { + var rnd = new Random(42); + byte[] bytes = new byte[length]; + rnd.NextBytes(bytes); + return bytes; + } + + internal static byte[] FillImageWithRandomBytes(Image image) + { + byte[] expected = TestUtils.GetRandomBytes(image.Width * image.Height * 2); + image.ProcessPixelRows(accessor => + { + int cnt = 0; + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new La16(expected[cnt++], expected[cnt++]); + } + } + }); + return expected; + } + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) where TPixel : unmanaged, IPixel {