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
{