Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 0 additions & 78 deletions src/ImageSharp/Advanced/AdvancedImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,84 +78,6 @@ public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel
where TPixel : unmanaged, IPixel<TPixel>
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));

IMemoryGroup<TPixel> mg = source.GetPixelMemoryGroup();
if (mg.Count > 1)
{
throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
}

return mg.Single().Span;
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));

return source.Frames.RootFrame.GetPixelSpan();
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

return source.PixelBuffer.GetRowSpan(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));

return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
Expand Down
34 changes: 34 additions & 0 deletions src/ImageSharp/ImageFrame{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
Expand Down Expand Up @@ -166,6 +167,39 @@ internal ImageFrame(Configuration configuration, ImageFrame<TPixel> source)
}
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs are missing <exception> for ArgumentOutOfRangeException.

public Span<TPixel> GetPixelRowSpan(int rowIndex)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));

return this.PixelBuffer.GetRowSpan(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count > 1)
{
span = default;
return false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antonfirsov Is there a test somewhere I can tap into to test the false condition?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can limit MemoryAllocator's BufferCapacity in many ways to ensure the backing memory is fragmented:

}

span = mg.Single().Span;
return true;
}

/// <summary>
/// Gets a reference to the pixel at the specified position.
/// </summary>
Expand Down
33 changes: 33 additions & 0 deletions src/ImageSharp/Image{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,39 @@ internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable<
}
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public Span<TPixel> GetPixelRowSpan(int rowIndex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous note.

{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));

return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex);
}

/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count > 1)
{
span = default;
return false;
}

span = mg.Single().Span;
return true;
}

/// <summary>
/// Clones the current image
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ public void ConsumedMemory_PixelDataIsCorrect<TPixel>(TestImageProvider<TPixel>
{
using Image<TPixel> image0 = provider.GetImage();
var targetBuffer = new TPixel[image0.Width * image0.Height];
image0.GetPixelSpan().CopyTo(targetBuffer);

Assert.True(image0.TryGetSinglePixelSpan(out Span<TPixel> sourceBuffer));

sourceBuffer.CopyTo(targetBuffer);

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

Expand Down
3 changes: 2 additions & 1 deletion tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ public void WorksWithDifferentLocations(TestImageProvider<Rgba32> provider, int
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50))
{
overlay.GetPixelSpan().Fill(Color.Black);
Assert.True(overlay.TryGetSinglePixelSpan(out Span<Rgba32> overlaySpan));
overlaySpan.Fill(Color.Black);

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

Expand Down
5 changes: 4 additions & 1 deletion tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ public void CanDecodeIntermingledImages()
{
ImageFrame<Rgba32> first = kumin1.Frames[i];
ImageFrame<Rgba32> second = kumin2.Frames[i];
first.ComparePixelBufferTo(second.GetPixelSpan());

Assert.True(second.TryGetSinglePixelSpan(out Span<Rgba32> secondSpan));

first.ComparePixelBufferTo(secondSpan);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;

namespace SixLabors.ImageSharp.Tests.Formats.Tga
{
Expand Down Expand Up @@ -46,7 +47,8 @@ public static Image<TPixel> DecodeWithMagick<TPixel>(Configuration configuration
{
magickImage.AutoOrient();
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
Span<TPixel> resultPixels = result.GetPixelSpan();

Assert.True(result.TryGetSinglePixelSpan(out Span<TPixel> resultPixels));

using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
{
Expand Down
22 changes: 15 additions & 7 deletions tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)
using (Image<TPixel> cloned = img.Frames.CloneFrame(0))
{
Assert.Equal(2, img.Frames.Count);
cloned.ComparePixelBufferTo(img.GetPixelSpan());
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));

cloned.ComparePixelBufferTo(imgSpan);
}
}
}
Expand All @@ -210,7 +212,8 @@ public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
{
using (Image<TPixel> img = provider.GetImage())
{
var sourcePixelData = img.GetPixelSpan().ToArray();
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
TPixel[] sourcePixelData = imgSpan.ToArray();

img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10));
using (Image<TPixel> cloned = img.Frames.ExportFrame(0))
Expand Down Expand Up @@ -242,7 +245,8 @@ public void CreateFrame_CustomFillColor()
[Fact]
public void AddFrameFromPixelData()
{
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray();
Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<Rgba32> imgSpan));
var pixelData = imgSpan.ToArray();
this.Image.Frames.AddFrame(pixelData);
Assert.Equal(2, this.Image.Frames.Count);
}
Expand All @@ -251,17 +255,21 @@ public void AddFrameFromPixelData()
public void AddFrame_clones_sourceFrame()
{
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
var addedFrame = this.Image.Frames.AddFrame(otherFrame);
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
ImageFrame<Rgba32> addedFrame = this.Image.Frames.AddFrame(otherFrame);

Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
addedFrame.ComparePixelBufferTo(otherFrameSpan);
Assert.NotEqual(otherFrame, addedFrame);
}

[Fact]
public void InsertFrame_clones_sourceFrame()
{
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
var addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
ImageFrame<Rgba32> addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);

Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
addedFrame.ComparePixelBufferTo(otherFrameSpan);
Assert.NotEqual(otherFrame, addedFrame);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ public void CloneFrame<TPixel>(TestImageProvider<TPixel> provider)

var expectedClone = (Image<TPixel>)cloned;

expectedClone.ComparePixelBufferTo(img.GetPixelSpan());
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
expectedClone.ComparePixelBufferTo(imgSpan);
}
}
}
Expand All @@ -171,7 +172,8 @@ public void ExtractFrame<TPixel>(TestImageProvider<TPixel> provider)
{
using (Image<TPixel> img = provider.GetImage())
{
var sourcePixelData = img.GetPixelSpan().ToArray();
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
var sourcePixelData = imgSpan.ToArray();

ImageFrameCollection nonGenericFrameCollection = img.Frames;

Expand Down
10 changes: 6 additions & 4 deletions tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ public void WrapMemory_CreatedImageIsCorrect()

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

Assert.Equal(cfg, image.GetConfiguration());
Expand All @@ -118,7 +119,8 @@ public void WrapSystemDrawingBitmap_WhenObserved()
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
{
Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory());
image.GetPixelSpan().Fill(bg);
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
Expand Down Expand Up @@ -153,8 +155,8 @@ public void WrapSystemDrawingBitmap_WhenOwned()
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
{
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory());

image.GetPixelSpan().Fill(bg);
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
Expand Down
9 changes: 6 additions & 3 deletions tests/ImageSharp.Tests/Image/ImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public void Width_Height()
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(default(Rgba32));

Assert.Equal(Configuration.Default, image.GetConfiguration());
Expand All @@ -43,7 +44,8 @@ public void Configuration_Width_Height()
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(default(Rgba32));

Assert.Equal(configuration, image.GetConfiguration());
Expand All @@ -60,7 +62,8 @@ public void Configuration_Width_Height_BackgroundColor()
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(color);

Assert.Equal(configuration, image.GetConfiguration());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ private static IResampler GetResampler(string name)
private static void VerifyAllPixelsAreWhiteOrTransparent<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> data = image.Frames.RootFrame.GetPixelSpan();
Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<TPixel> data));
var white = new Rgb24(255, 255, 255);
foreach (TPixel pixel in data)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ public void Resize_ThrowsForWrappedMemoryImage<TPixel>(TestImageProvider<TPixel>
{
using (Image<TPixel> image0 = provider.GetImage())
{
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOf(image0.GetPixelSpan());
Assert.True(image0.TryGetSinglePixelSpan(out Span<TPixel> imageSpan));
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOf(imageSpan);

using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height))
{
Expand Down
Loading