Skip to content

Commit 87c0a66

Browse files
authored
Merge pull request #888 from SixLabors/af/resize-sandbox
Limit ResizeProcessor memory consumption
2 parents bcd5f02 + 58f8d63 commit 87c0a66

36 files changed

+1891
-860
lines changed

src/ImageSharp/Configuration.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ public int MaxDegreeOfParallelism
102102
/// </summary>
103103
internal IFileSystem FileSystem { get; set; } = new LocalFileSystem();
104104

105+
/// <summary>
106+
/// Gets or sets the working buffer size hint for image processors.
107+
/// The default value is 1MB.
108+
/// </summary>
109+
/// <remarks>
110+
/// Currently only used by Resize.
111+
/// </remarks>
112+
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;
113+
105114
/// <summary>
106115
/// Gets or sets the image operations provider factory.
107116
/// </summary>
@@ -118,9 +127,9 @@ public void Configure(IConfigurationModule configuration)
118127
}
119128

120129
/// <summary>
121-
/// Creates a shallow copy of the <see cref="Configuration"/>
130+
/// Creates a shallow copy of the <see cref="Configuration"/>.
122131
/// </summary>
123-
/// <returns>A new configuration instance</returns>
132+
/// <returns>A new configuration instance.</returns>
124133
public Configuration Clone()
125134
{
126135
return new Configuration
@@ -130,18 +139,19 @@ public Configuration Clone()
130139
MemoryAllocator = this.MemoryAllocator,
131140
ImageOperationsProvider = this.ImageOperationsProvider,
132141
ReadOrigin = this.ReadOrigin,
133-
FileSystem = this.FileSystem
142+
FileSystem = this.FileSystem,
143+
WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
134144
};
135145
}
136146

137147
/// <summary>
138148
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
139-
/// <para><see cref="PngConfigurationModule"/></para>
140-
/// <para><see cref="JpegConfigurationModule"/></para>
141-
/// <para><see cref="GifConfigurationModule"/></para>
142-
/// <para><see cref="BmpConfigurationModule"/></para>
149+
/// <see cref="PngConfigurationModule"/>
150+
/// <see cref="JpegConfigurationModule"/>
151+
/// <see cref="GifConfigurationModule"/>
152+
/// <see cref="BmpConfigurationModule"/>.
143153
/// </summary>
144-
/// <returns>The default configuration of <see cref="Configuration"/></returns>
154+
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
145155
internal static Configuration CreateDefaultInstance()
146156
{
147157
return new Configuration(

src/ImageSharp/Memory/Buffer2DExtensions.cs

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

44
using System;
5+
using System.Buffers;
6+
using System.Diagnostics;
57
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
69

710
using SixLabors.Primitives;
811

@@ -14,55 +17,135 @@ namespace SixLabors.ImageSharp.Memory
1417
internal static class Buffer2DExtensions
1518
{
1619
/// <summary>
17-
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
20+
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
21+
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
1822
/// </summary>
19-
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
23+
public static unsafe void CopyColumns<T>(
24+
this Buffer2D<T> buffer,
25+
int sourceIndex,
26+
int destIndex,
27+
int columnCount)
2028
where T : struct
2129
{
22-
return buffer.MemorySource.GetSpan();
30+
DebugGuard.NotNull(buffer, nameof(buffer));
31+
DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex));
32+
DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex));
33+
CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount);
34+
35+
int elementSize = Unsafe.SizeOf<T>();
36+
int width = buffer.Width * elementSize;
37+
int sOffset = sourceIndex * elementSize;
38+
int dOffset = destIndex * elementSize;
39+
long count = columnCount * elementSize;
40+
41+
Span<byte> span = MemoryMarshal.AsBytes(buffer.Memory.Span);
42+
43+
fixed (byte* ptr = span)
44+
{
45+
byte* basePtr = (byte*)ptr;
46+
for (int y = 0; y < buffer.Height; y++)
47+
{
48+
byte* sPtr = basePtr + sOffset;
49+
byte* dPtr = basePtr + dOffset;
50+
51+
Buffer.MemoryCopy(sPtr, dPtr, count, count);
52+
53+
basePtr += width;
54+
}
55+
}
2356
}
2457

2558
/// <summary>
26-
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
59+
/// Returns a <see cref="Rectangle"/> representing the full area of the buffer.
60+
/// </summary>
61+
/// <typeparam name="T">The element type</typeparam>
62+
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
63+
/// <returns>The <see cref="Rectangle"/></returns>
64+
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
65+
where T : struct
66+
{
67+
return new Rectangle(0, 0, buffer.Width, buffer.Height);
68+
}
69+
70+
/// <summary>
71+
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle'
72+
/// </summary>
73+
/// <typeparam name="T">The element type</typeparam>
74+
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
75+
/// <param name="rectangle">The rectangle subarea</param>
76+
/// <returns>The <see cref="BufferArea{T}"/></returns>
77+
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
78+
where T : struct =>
79+
new BufferArea<T>(buffer, rectangle);
80+
81+
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
82+
where T : struct =>
83+
new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
84+
85+
/// <summary>
86+
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer'
87+
/// </summary>
88+
/// <typeparam name="T">The element type</typeparam>
89+
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
90+
/// <returns>The <see cref="BufferArea{T}"/></returns>
91+
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
92+
where T : struct =>
93+
new BufferArea<T>(buffer);
94+
95+
public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
96+
where T : struct =>
97+
new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
98+
99+
/// <summary>
100+
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
101+
/// </summary>
102+
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
103+
where T : struct
104+
{
105+
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
106+
}
107+
108+
/// <summary>
109+
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
27110
/// </summary>
28111
/// <param name="buffer">The buffer</param>
29-
/// <param name="x">The x coordinate (position in the row)</param>
30112
/// <param name="y">The y (row) coordinate</param>
31113
/// <typeparam name="T">The element type</typeparam>
32114
/// <returns>The <see cref="Span{T}"/></returns>
33115
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34-
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
116+
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
35117
where T : struct
36118
{
37-
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
119+
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
38120
}
39121

40122
/// <summary>
41-
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
123+
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
42124
/// </summary>
43125
/// <param name="buffer">The buffer</param>
126+
/// <param name="x">The x coordinate (position in the row)</param>
44127
/// <param name="y">The y (row) coordinate</param>
45128
/// <typeparam name="T">The element type</typeparam>
46129
/// <returns>The <see cref="Span{T}"/></returns>
47130
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48-
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
131+
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
49132
where T : struct
50133
{
51-
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
134+
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
52135
}
53136

54137
/// <summary>
55-
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
138+
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
56139
/// </summary>
57140
/// <param name="buffer">The buffer</param>
58141
/// <param name="y">The y (row) coordinate</param>
59142
/// <typeparam name="T">The element type</typeparam>
60143
/// <returns>The <see cref="Span{T}"/></returns>
61144
[MethodImpl(MethodImplOptions.AggressiveInlining)]
62-
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
145+
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
63146
where T : struct
64147
{
65-
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
148+
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
66149
}
67150

68151
/// <summary>
@@ -78,49 +161,28 @@ public static Size Size<T>(this Buffer2D<T> buffer)
78161
}
79162

80163
/// <summary>
81-
/// Returns a <see cref="Rectangle"/> representing the full area of the buffer.
164+
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
82165
/// </summary>
83-
/// <typeparam name="T">The element type</typeparam>
84-
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
85-
/// <returns>The <see cref="Rectangle"/></returns>
86-
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
166+
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
87167
where T : struct
88168
{
89-
return new Rectangle(0, 0, buffer.Width, buffer.Height);
169+
return buffer.MemorySource.GetSpan();
90170
}
91171

92-
/// <summary>
93-
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle'
94-
/// </summary>
95-
/// <typeparam name="T">The element type</typeparam>
96-
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
97-
/// <param name="rectangle">The rectangle subarea</param>
98-
/// <returns>The <see cref="BufferArea{T}"/></returns>
99-
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
100-
where T : struct => new BufferArea<T>(buffer, rectangle);
101-
102-
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
103-
where T : struct => new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
104-
105-
public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
106-
where T : struct => new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
107-
108-
/// <summary>
109-
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer'
110-
/// </summary>
111-
/// <typeparam name="T">The element type</typeparam>
112-
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
113-
/// <returns>The <see cref="BufferArea{T}"/></returns>
114-
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
115-
where T : struct => new BufferArea<T>(buffer);
116-
117-
/// <summary>
118-
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
119-
/// </summary>
120-
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
172+
[Conditional("DEBUG")]
173+
private static void CheckColumnRegionsDoNotOverlap<T>(
174+
Buffer2D<T> buffer,
175+
int sourceIndex,
176+
int destIndex,
177+
int columnCount)
121178
where T : struct
122179
{
123-
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
180+
int minIndex = Math.Min(sourceIndex, destIndex);
181+
int maxIndex = Math.Max(sourceIndex, destIndex);
182+
if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount)
183+
{
184+
throw new InvalidOperationException("Column regions should not overlap!");
185+
}
124186
}
125187
}
126188
}

src/ImageSharp/Memory/RowInterval.cs

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

4+
using System;
5+
46
using SixLabors.Primitives;
57

68
namespace SixLabors.ImageSharp.Memory
79
{
810
/// <summary>
911
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
1012
/// </summary>
11-
internal readonly struct RowInterval
13+
internal readonly struct RowInterval : IEquatable<RowInterval>
1214
{
1315
/// <summary>
1416
/// Initializes a new instance of the <see cref="RowInterval"/> struct.
@@ -36,7 +38,33 @@ public RowInterval(int min, int max)
3638
/// </summary>
3739
public int Height => this.Max - this.Min;
3840

41+
public static bool operator ==(RowInterval left, RowInterval right)
42+
{
43+
return left.Equals(right);
44+
}
45+
46+
public static bool operator !=(RowInterval left, RowInterval right)
47+
{
48+
return !left.Equals(right);
49+
}
50+
3951
/// <inheritdoc />
4052
public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]";
53+
54+
public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max);
55+
56+
public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length);
57+
58+
public bool Equals(RowInterval other)
59+
{
60+
return this.Min == other.Min && this.Max == other.Max;
61+
}
62+
63+
public override bool Equals(object obj)
64+
{
65+
return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other);
66+
}
67+
68+
public override int GetHashCode() => HashCode.Combine(this.Min, this.Max);
4169
}
4270
}

src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
namespace SixLabors.ImageSharp.PixelFormats
77
{
8+
/// <summary>
9+
/// Extension and utility methods for <see cref="PixelConversionModifiers"/>.
10+
/// </summary>
811
internal static class PixelConversionModifiersExtensions
912
{
1013
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -16,5 +19,20 @@ public static PixelConversionModifiers Remove(
1619
this PixelConversionModifiers modifiers,
1720
PixelConversionModifiers removeThis) =>
1821
modifiers & ~removeThis;
22+
23+
/// <summary>
24+
/// Applies the union of <see cref="PixelConversionModifiers.Scale"/> and <see cref="PixelConversionModifiers.SRgbCompand"/>,
25+
/// if <paramref name="compand"/> is true, returns unmodified <paramref name="originalModifiers"/> otherwise.
26+
/// </summary>
27+
/// <remarks>
28+
/// <see cref="PixelConversionModifiers.Scale"/> and <see cref="PixelConversionModifiers.SRgbCompand"/>
29+
/// should be always used together!
30+
/// </remarks>
31+
public static PixelConversionModifiers ApplyCompanding(
32+
this PixelConversionModifiers originalModifiers,
33+
bool compand) =>
34+
compand
35+
? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand
36+
: originalModifiers;
1937
}
2038
}

0 commit comments

Comments
 (0)