Skip to content

Commit de73c63

Browse files
Merge pull request #1172 from SixLabors/js/span-accessibility
Improve accessability of Span<T> methods.
2 parents ddfe643 + 2ec7768 commit de73c63

File tree

16 files changed

+135
-109
lines changed

16 files changed

+135
-109
lines changed

src/ImageSharp/Advanced/AdvancedImageExtensions.cs

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -78,84 +78,6 @@ public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel
7878
where TPixel : unmanaged, IPixel<TPixel>
7979
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
8080

81-
/// <summary>
82-
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
83-
/// stored in row major order, if the backing buffer is contiguous.
84-
/// </summary>
85-
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
86-
/// <param name="source">The source image.</param>
87-
/// <returns>The <see cref="Span{TPixel}"/></returns>
88-
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
89-
[Obsolete(
90-
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
91-
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
92-
where TPixel : unmanaged, IPixel<TPixel>
93-
{
94-
Guard.NotNull(source, nameof(source));
95-
96-
IMemoryGroup<TPixel> mg = source.GetPixelMemoryGroup();
97-
if (mg.Count > 1)
98-
{
99-
throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
100-
}
101-
102-
return mg.Single().Span;
103-
}
104-
105-
/// <summary>
106-
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
107-
/// stored in row major order.
108-
/// </summary>
109-
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
110-
/// <param name="source">The source.</param>
111-
/// <returns>The <see cref="Span{TPixel}"/></returns>
112-
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
113-
[Obsolete(
114-
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
115-
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
116-
where TPixel : unmanaged, IPixel<TPixel>
117-
{
118-
Guard.NotNull(source, nameof(source));
119-
120-
return source.Frames.RootFrame.GetPixelSpan();
121-
}
122-
123-
/// <summary>
124-
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
125-
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
126-
/// </summary>
127-
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
128-
/// <param name="source">The source.</param>
129-
/// <param name="rowIndex">The row.</param>
130-
/// <returns>The <see cref="Span{TPixel}"/></returns>
131-
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
132-
where TPixel : unmanaged, IPixel<TPixel>
133-
{
134-
Guard.NotNull(source, nameof(source));
135-
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
136-
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
137-
138-
return source.PixelBuffer.GetRowSpan(rowIndex);
139-
}
140-
141-
/// <summary>
142-
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
143-
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
144-
/// </summary>
145-
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
146-
/// <param name="source">The source.</param>
147-
/// <param name="rowIndex">The row.</param>
148-
/// <returns>The <see cref="Span{TPixel}"/></returns>
149-
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
150-
where TPixel : unmanaged, IPixel<TPixel>
151-
{
152-
Guard.NotNull(source, nameof(source));
153-
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
154-
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
155-
156-
return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
157-
}
158-
15981
/// <summary>
16082
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
16183
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.

src/ImageSharp/ImageFrame{TPixel}.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Linq;
56
using System.Runtime.CompilerServices;
67
using System.Runtime.InteropServices;
78
using SixLabors.ImageSharp.Advanced;
@@ -166,6 +167,40 @@ internal ImageFrame(Configuration configuration, ImageFrame<TPixel> source)
166167
}
167168
}
168169

170+
/// <summary>
171+
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
172+
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
173+
/// </summary>
174+
/// <param name="rowIndex">The row.</param>
175+
/// <returns>The <see cref="Span{TPixel}"/></returns>
176+
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
177+
public Span<TPixel> GetPixelRowSpan(int rowIndex)
178+
{
179+
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
180+
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
181+
182+
return this.PixelBuffer.GetRowSpan(rowIndex);
183+
}
184+
185+
/// <summary>
186+
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
187+
/// stored in row major order, if the backing buffer is contiguous.
188+
/// </summary>
189+
/// <param name="span">The <see cref="Span{T}"/>.</param>
190+
/// <returns>The <see cref="bool"/>.</returns>
191+
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
192+
{
193+
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
194+
if (mg.Count > 1)
195+
{
196+
span = default;
197+
return false;
198+
}
199+
200+
span = mg.Single().Span;
201+
return true;
202+
}
203+
169204
/// <summary>
170205
/// Gets a reference to the pixel at the specified position.
171206
/// </summary>

src/ImageSharp/Image{TPixel}.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,40 @@ internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable<
163163
}
164164
}
165165

166+
/// <summary>
167+
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
168+
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
169+
/// </summary>
170+
/// <param name="rowIndex">The row.</param>
171+
/// <returns>The <see cref="Span{TPixel}"/></returns>
172+
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
173+
public Span<TPixel> GetPixelRowSpan(int rowIndex)
174+
{
175+
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
176+
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
177+
178+
return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex);
179+
}
180+
181+
/// <summary>
182+
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
183+
/// stored in row major order, if the backing buffer is contiguous.
184+
/// </summary>
185+
/// <param name="span">The <see cref="Span{T}"/>.</param>
186+
/// <returns>The <see cref="bool"/>.</returns>
187+
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
188+
{
189+
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
190+
if (mg.Count > 1)
191+
{
192+
span = default;
193+
return false;
194+
}
195+
196+
span = mg.Single().Span;
197+
return true;
198+
}
199+
166200
/// <summary>
167201
/// Clones the current image
168202
/// </summary>

tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ public void ConsumedMemory_PixelDataIsCorrect<TPixel>(TestImageProvider<TPixel>
6161
{
6262
using Image<TPixel> image0 = provider.GetImage();
6363
var targetBuffer = new TPixel[image0.Width * image0.Height];
64-
image0.GetPixelSpan().CopyTo(targetBuffer);
64+
65+
Assert.True(image0.TryGetSinglePixelSpan(out Span<TPixel> sourceBuffer));
66+
67+
sourceBuffer.CopyTo(targetBuffer);
6568

6669
var managerOfExternalMemory = new TestMemoryManager<TPixel>(targetBuffer);
6770

tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ public void WorksWithDifferentLocations(TestImageProvider<Rgba32> provider, int
128128
using (Image<Rgba32> background = provider.GetImage())
129129
using (var overlay = new Image<Rgba32>(50, 50))
130130
{
131-
overlay.GetPixelSpan().Fill(Color.Black);
131+
Assert.True(overlay.TryGetSinglePixelSpan(out Span<Rgba32> overlaySpan));
132+
overlaySpan.Fill(Color.Black);
132133

133134
background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F));
134135

tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ public void CanDecodeIntermingledImages()
164164
{
165165
ImageFrame<Rgba32> first = kumin1.Frames[i];
166166
ImageFrame<Rgba32> second = kumin2.Frames[i];
167-
first.ComparePixelBufferTo(second.GetPixelSpan());
167+
168+
Assert.True(second.TryGetSinglePixelSpan(out Span<Rgba32> secondSpan));
169+
170+
first.ComparePixelBufferTo(secondSpan);
168171
}
169172
}
170173
}

tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using SixLabors.ImageSharp.Advanced;
1010
using SixLabors.ImageSharp.PixelFormats;
1111
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
12+
using Xunit;
1213

1314
namespace SixLabors.ImageSharp.Tests.Formats.Tga
1415
{
@@ -46,7 +47,8 @@ public static Image<TPixel> DecodeWithMagick<TPixel>(Configuration configuration
4647
{
4748
magickImage.AutoOrient();
4849
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
49-
Span<TPixel> resultPixels = result.GetPixelSpan();
50+
51+
Assert.True(result.TryGetSinglePixelSpan(out Span<TPixel> resultPixels));
5052

5153
using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
5254
{

tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)
198198
using (Image<TPixel> cloned = img.Frames.CloneFrame(0))
199199
{
200200
Assert.Equal(2, img.Frames.Count);
201-
cloned.ComparePixelBufferTo(img.GetPixelSpan());
201+
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
202+
203+
cloned.ComparePixelBufferTo(imgSpan);
202204
}
203205
}
204206
}
@@ -210,7 +212,8 @@ public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
210212
{
211213
using (Image<TPixel> img = provider.GetImage())
212214
{
213-
var sourcePixelData = img.GetPixelSpan().ToArray();
215+
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
216+
TPixel[] sourcePixelData = imgSpan.ToArray();
214217

215218
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10));
216219
using (Image<TPixel> cloned = img.Frames.ExportFrame(0))
@@ -242,7 +245,8 @@ public void CreateFrame_CustomFillColor()
242245
[Fact]
243246
public void AddFrameFromPixelData()
244247
{
245-
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray();
248+
Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<Rgba32> imgSpan));
249+
var pixelData = imgSpan.ToArray();
246250
this.Image.Frames.AddFrame(pixelData);
247251
Assert.Equal(2, this.Image.Frames.Count);
248252
}
@@ -251,17 +255,21 @@ public void AddFrameFromPixelData()
251255
public void AddFrame_clones_sourceFrame()
252256
{
253257
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
254-
var addedFrame = this.Image.Frames.AddFrame(otherFrame);
255-
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
258+
ImageFrame<Rgba32> addedFrame = this.Image.Frames.AddFrame(otherFrame);
259+
260+
Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
261+
addedFrame.ComparePixelBufferTo(otherFrameSpan);
256262
Assert.NotEqual(otherFrame, addedFrame);
257263
}
258264

259265
[Fact]
260266
public void InsertFrame_clones_sourceFrame()
261267
{
262268
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
263-
var addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
264-
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
269+
ImageFrame<Rgba32> addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
270+
271+
Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
272+
addedFrame.ComparePixelBufferTo(otherFrameSpan);
265273
Assert.NotEqual(otherFrame, addedFrame);
266274
}
267275

tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)
159159

160160
var expectedClone = (Image<TPixel>)cloned;
161161

162-
expectedClone.ComparePixelBufferTo(img.GetPixelSpan());
162+
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
163+
expectedClone.ComparePixelBufferTo(imgSpan);
163164
}
164165
}
165166
}
@@ -171,7 +172,8 @@ public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
171172
{
172173
using (Image<TPixel> img = provider.GetImage())
173174
{
174-
var sourcePixelData = img.GetPixelSpan().ToArray();
175+
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
176+
var sourcePixelData = imgSpan.ToArray();
175177

176178
ImageFrameCollection nonGenericFrameCollection = img.Frames;
177179

tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ public void WrapMemory_CreatedImageIsCorrect()
9191

9292
using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData))
9393
{
94-
ref Rgba32 pixel0 = ref image.GetPixelSpan()[0];
94+
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
95+
ref Rgba32 pixel0 = ref imageSpan[0];
9596
Assert.True(Unsafe.AreSame(ref array[0], ref pixel0));
9697

9798
Assert.Equal(cfg, image.GetConfiguration());
@@ -118,7 +119,8 @@ public void WrapSystemDrawingBitmap_WhenObserved()
118119
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
119120
{
120121
Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory());
121-
image.GetPixelSpan().Fill(bg);
122+
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
123+
imageSpan.Fill(bg);
122124
for (var i = 10; i < 20; i++)
123125
{
124126
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
@@ -153,8 +155,8 @@ public void WrapSystemDrawingBitmap_WhenOwned()
153155
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
154156
{
155157
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory());
156-
157-
image.GetPixelSpan().Fill(bg);
158+
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
159+
imageSpan.Fill(bg);
158160
for (var i = 10; i < 20; i++)
159161
{
160162
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);

0 commit comments

Comments
 (0)