Skip to content

Commit db5ea65

Browse files
Merge pull request #1109 from SixLabors/af/disco-buffers
Implement discontiguous buffer handling
2 parents 20f4bfb + ad2632f commit db5ea65

File tree

112 files changed

+3731
-1001
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+3731
-1001
lines changed

src/ImageSharp/Advanced/AdvancedImageExtensions.cs

Lines changed: 84 additions & 71 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.InteropServices;
67

78
using SixLabors.ImageSharp.Memory;
@@ -40,23 +41,66 @@ public static Configuration GetConfiguration(this ImageFrame source)
4041
=> GetConfiguration((IConfigurationProvider)source);
4142

4243
/// <summary>
43-
/// Gets the configuration .
44+
/// Gets the configuration.
4445
/// </summary>
4546
/// <param name="source">The source image</param>
4647
/// <returns>Returns the bounds of the image</returns>
4748
private static Configuration GetConfiguration(IConfigurationProvider source)
4849
=> source?.Configuration ?? Configuration.Default;
4950

5051
/// <summary>
51-
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
52-
/// stored in row major order.
52+
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
53+
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
5354
/// </summary>
55+
/// <param name="source">The source image.</param>
5456
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
55-
/// <param name="source">The source.</param>
57+
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
58+
/// <remarks>
59+
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
60+
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
61+
/// </remarks>
62+
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this ImageFrame<TPixel> source)
63+
where TPixel : struct, IPixel<TPixel>
64+
=> source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source));
65+
66+
/// <summary>
67+
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
68+
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
69+
/// </summary>
70+
/// <param name="source">The source image.</param>
71+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
72+
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
73+
/// <remarks>
74+
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
75+
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
76+
/// </remarks>
77+
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel> source)
78+
where TPixel : struct, IPixel<TPixel>
79+
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
80+
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>
5687
/// <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!")]
5791
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
5892
where TPixel : struct, IPixel<TPixel>
59-
=> source.GetPixelMemory().Span;
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+
}
60104

61105
/// <summary>
62106
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
@@ -65,9 +109,16 @@ public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
65109
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
66110
/// <param name="source">The source.</param>
67111
/// <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!")]
68115
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
69116
where TPixel : struct, IPixel<TPixel>
70-
=> source.Frames.RootFrame.GetPixelSpan();
117+
{
118+
Guard.NotNull(source, nameof(source));
119+
120+
return source.Frames.RootFrame.GetPixelSpan();
121+
}
71122

72123
/// <summary>
73124
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
@@ -79,7 +130,13 @@ public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
79130
/// <returns>The <see cref="Span{TPixel}"/></returns>
80131
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
81132
where TPixel : struct, IPixel<TPixel>
82-
=> source.PixelBuffer.GetRowSpan(rowIndex);
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+
}
83140

84141
/// <summary>
85142
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@@ -91,58 +148,12 @@ public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> sourc
91148
/// <returns>The <see cref="Span{TPixel}"/></returns>
92149
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
93150
where TPixel : struct, IPixel<TPixel>
94-
=> source.Frames.RootFrame.GetPixelRowSpan(rowIndex);
95-
96-
/// <summary>
97-
/// Returns a reference to the 0th element of the Pixel buffer,
98-
/// allowing direct manipulation of pixel data through unsafe operations.
99-
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
100-
/// </summary>
101-
/// <typeparam name="TPixel">The Pixel format.</typeparam>
102-
/// <param name="source">The source image frame</param>
103-
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
104-
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
105-
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
106-
where TPixel : struct, IPixel<TPixel>
107-
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);
108-
109-
/// <summary>
110-
/// Returns a reference to the 0th element of the Pixel buffer,
111-
/// allowing direct manipulation of pixel data through unsafe operations.
112-
/// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
113-
/// </summary>
114-
/// <typeparam name="TPixel">The Pixel format.</typeparam>
115-
/// <param name="source">The source image</param>
116-
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
117-
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
118-
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
119-
where TPixel : struct, IPixel<TPixel>
120-
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
121-
122-
/// <summary>
123-
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
124-
/// stored in row major order.
125-
/// </summary>
126-
/// <typeparam name="TPixel">The Pixel format.</typeparam>
127-
/// <param name="source">The source <see cref="ImageFrame{TPixel}"/></param>
128-
/// <returns>The <see cref="Memory{T}"/></returns>
129-
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
130-
where TPixel : struct, IPixel<TPixel>
131151
{
132-
return source.PixelBuffer.MemorySource.Memory;
133-
}
152+
Guard.NotNull(source, nameof(source));
153+
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
154+
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
134155

135-
/// <summary>
136-
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
137-
/// stored in row major order.
138-
/// </summary>
139-
/// <typeparam name="TPixel">The Pixel format.</typeparam>
140-
/// <param name="source">The source <see cref="Image{TPixel}"/></param>
141-
/// <returns>The <see cref="Memory{T}"/></returns>
142-
internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
143-
where TPixel : struct, IPixel<TPixel>
144-
{
145-
return source.Frames.RootFrame.GetPixelMemory();
156+
return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
146157
}
147158

148159
/// <summary>
@@ -153,9 +164,15 @@ internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
153164
/// <param name="source">The source.</param>
154165
/// <param name="rowIndex">The row.</param>
155166
/// <returns>The <see cref="Span{TPixel}"/></returns>
156-
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
167+
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
157168
where TPixel : struct, IPixel<TPixel>
158-
=> source.PixelBuffer.GetRowMemory(rowIndex);
169+
{
170+
Guard.NotNull(source, nameof(source));
171+
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
172+
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
173+
174+
return source.PixelBuffer.GetSafeRowMemory(rowIndex);
175+
}
159176

160177
/// <summary>
161178
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@@ -165,9 +182,15 @@ internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel>
165182
/// <param name="source">The source.</param>
166183
/// <param name="rowIndex">The row.</param>
167184
/// <returns>The <see cref="Span{TPixel}"/></returns>
168-
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
185+
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
169186
where TPixel : struct, IPixel<TPixel>
170-
=> source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
187+
{
188+
Guard.NotNull(source, nameof(source));
189+
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
190+
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
191+
192+
return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
193+
}
171194

172195
/// <summary>
173196
/// Gets the <see cref="MemoryAllocator"/> assigned to 'source'.
@@ -176,15 +199,5 @@ internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> sour
176199
/// <returns>Returns the configuration.</returns>
177200
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
178201
=> GetConfiguration(source).MemoryAllocator;
179-
180-
/// <summary>
181-
/// Returns a reference to the 0th element of the Pixel buffer.
182-
/// Such a reference can be used for pinning but must never be dereferenced.
183-
/// </summary>
184-
/// <param name="source">The source image frame</param>
185-
/// <returns>A reference to the element.</returns>
186-
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
187-
where TPixel : struct, IPixel<TPixel>
188-
=> ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
189202
}
190203
}

src/ImageSharp/Common/Exceptions/ImageFormatException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
77
{
88
/// <summary>
99
/// The exception that is thrown when the library tries to load
10-
/// an image, which has an invalid format.
10+
/// an image, which has format or content that is invalid or unsupported by ImageSharp.
1111
/// </summary>
1212
public class ImageFormatException : Exception
1313
{

src/ImageSharp/Configuration.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ public int MaxDegreeOfParallelism
108108
/// The default value is 1MB.
109109
/// </summary>
110110
/// <remarks>
111-
/// Currently only used by Resize.
111+
/// Currently only used by Resize. If the working buffer is expected to be discontiguous,
112+
/// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used.
112113
/// </remarks>
113114
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;
114115

src/ImageSharp/Formats/Bmp/BmpDecoder.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.IO;
5+
using SixLabors.ImageSharp.Memory;
56
using SixLabors.ImageSharp.PixelFormats;
67

78
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -32,7 +33,20 @@ public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3233
{
3334
Guard.NotNull(stream, nameof(stream));
3435

35-
return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
36+
var decoder = new BmpDecoderCore(configuration, this);
37+
38+
try
39+
{
40+
return decoder.Decode<TPixel>(stream);
41+
}
42+
catch (InvalidMemoryOperationException ex)
43+
{
44+
Size dims = decoder.Dimensions;
45+
46+
// TODO: use InvalidImageContentException here, if we decide to define it
47+
// https://github.com/SixLabors/ImageSharp/issues/1110
48+
throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
49+
}
3650
}
3751

3852
/// <inheritdoc />

0 commit comments

Comments
 (0)