Skip to content

Commit

Permalink
Merge pull request SixLabors#888 from SixLabors/af/resize-sandbox
Browse files Browse the repository at this point in the history
Limit ResizeProcessor memory consumption
  • Loading branch information
antonfirsov authored Apr 22, 2019
2 parents bcd5f02 + 58f8d63 commit 87c0a66
Show file tree
Hide file tree
Showing 36 changed files with 1,891 additions and 860 deletions.
26 changes: 18 additions & 8 deletions src/ImageSharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ public int MaxDegreeOfParallelism
/// </summary>
internal IFileSystem FileSystem { get; set; } = new LocalFileSystem();

/// <summary>
/// Gets or sets the working buffer size hint for image processors.
/// The default value is 1MB.
/// </summary>
/// <remarks>
/// Currently only used by Resize.
/// </remarks>
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;

/// <summary>
/// Gets or sets the image operations provider factory.
/// </summary>
Expand All @@ -118,9 +127,9 @@ public void Configure(IConfigurationModule configuration)
}

/// <summary>
/// Creates a shallow copy of the <see cref="Configuration"/>
/// Creates a shallow copy of the <see cref="Configuration"/>.
/// </summary>
/// <returns>A new configuration instance</returns>
/// <returns>A new configuration instance.</returns>
public Configuration Clone()
{
return new Configuration
Expand All @@ -130,18 +139,19 @@ public Configuration Clone()
MemoryAllocator = this.MemoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider,
ReadOrigin = this.ReadOrigin,
FileSystem = this.FileSystem
FileSystem = this.FileSystem,
WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
};
}

/// <summary>
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
/// <para><see cref="PngConfigurationModule"/></para>
/// <para><see cref="JpegConfigurationModule"/></para>
/// <para><see cref="GifConfigurationModule"/></para>
/// <para><see cref="BmpConfigurationModule"/></para>
/// <see cref="PngConfigurationModule"/>
/// <see cref="JpegConfigurationModule"/>
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/></returns>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance()
{
return new Configuration(
Expand Down
160 changes: 111 additions & 49 deletions src/ImageSharp/Memory/Buffer2DExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using SixLabors.Primitives;

Expand All @@ -14,55 +17,135 @@ namespace SixLabors.ImageSharp.Memory
internal static class Buffer2DExtensions
{
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary>
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
public static unsafe void CopyColumns<T>(
this Buffer2D<T> buffer,
int sourceIndex,
int destIndex,
int columnCount)
where T : struct
{
return buffer.MemorySource.GetSpan();
DebugGuard.NotNull(buffer, nameof(buffer));
DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex));
DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex));
CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount);

int elementSize = Unsafe.SizeOf<T>();
int width = buffer.Width * elementSize;
int sOffset = sourceIndex * elementSize;
int dOffset = destIndex * elementSize;
long count = columnCount * elementSize;

Span<byte> span = MemoryMarshal.AsBytes(buffer.Memory.Span);

fixed (byte* ptr = span)
{
byte* basePtr = (byte*)ptr;
for (int y = 0; y < buffer.Height; y++)
{
byte* sPtr = basePtr + sOffset;
byte* dPtr = basePtr + dOffset;

Buffer.MemoryCopy(sPtr, dPtr, count, count);

basePtr += width;
}
}
}

/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// Returns a <see cref="Rectangle"/> representing the full area of the buffer.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
where T : struct
{
return new Rectangle(0, 0, buffer.Width, buffer.Height);
}

/// <summary>
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle'
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct =>
new BufferArea<T>(buffer, rectangle);

public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(x, y, width, height));

/// <summary>
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer'
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
where T : struct =>
new BufferArea<T>(buffer);

public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));

/// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary>
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
where T : struct
{
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}

/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="x">The x coordinate (position in the row)</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}

/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="x">The x coordinate (position in the row)</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
where T : struct
{
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
}

/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
}

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

/// <summary>
/// Returns a <see cref="Rectangle"/> representing the full area of the buffer.
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
return new Rectangle(0, 0, buffer.Width, buffer.Height);
return buffer.MemorySource.GetSpan();
}

/// <summary>
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle'
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct => new BufferArea<T>(buffer, rectangle);

public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct => new BufferArea<T>(buffer, new Rectangle(x, y, width, height));

public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
where T : struct => new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));

/// <summary>
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer'
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
where T : struct => new BufferArea<T>(buffer);

/// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary>
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
[Conditional("DEBUG")]
private static void CheckColumnRegionsDoNotOverlap<T>(
Buffer2D<T> buffer,
int sourceIndex,
int destIndex,
int columnCount)
where T : struct
{
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
int minIndex = Math.Min(sourceIndex, destIndex);
int maxIndex = Math.Max(sourceIndex, destIndex);
if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount)
{
throw new InvalidOperationException("Column regions should not overlap!");
}
}
}
}
30 changes: 29 additions & 1 deletion src/ImageSharp/Memory/RowInterval.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;

using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
/// </summary>
internal readonly struct RowInterval
internal readonly struct RowInterval : IEquatable<RowInterval>
{
/// <summary>
/// Initializes a new instance of the <see cref="RowInterval"/> struct.
Expand Down Expand Up @@ -36,7 +38,33 @@ public RowInterval(int min, int max)
/// </summary>
public int Height => this.Max - this.Min;

public static bool operator ==(RowInterval left, RowInterval right)
{
return left.Equals(right);
}

public static bool operator !=(RowInterval left, RowInterval right)
{
return !left.Equals(right);
}

/// <inheritdoc />
public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]";

public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max);

public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length);

public bool Equals(RowInterval other)
{
return this.Min == other.Min && this.Max == other.Max;
}

public override bool Equals(object obj)
{
return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other);
}

public override int GetHashCode() => HashCode.Combine(this.Min, this.Max);
}
}
18 changes: 18 additions & 0 deletions src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Extension and utility methods for <see cref="PixelConversionModifiers"/>.
/// </summary>
internal static class PixelConversionModifiersExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -16,5 +19,20 @@ public static PixelConversionModifiers Remove(
this PixelConversionModifiers modifiers,
PixelConversionModifiers removeThis) =>
modifiers & ~removeThis;

/// <summary>
/// Applies the union of <see cref="PixelConversionModifiers.Scale"/> and <see cref="PixelConversionModifiers.SRgbCompand"/>,
/// if <paramref name="compand"/> is true, returns unmodified <paramref name="originalModifiers"/> otherwise.
/// </summary>
/// <remarks>
/// <see cref="PixelConversionModifiers.Scale"/> and <see cref="PixelConversionModifiers.SRgbCompand"/>
/// should be always used together!
/// </remarks>
public static PixelConversionModifiers ApplyCompanding(
this PixelConversionModifiers originalModifiers,
bool compand) =>
compand
? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand
: originalModifiers;
}
}
Loading

0 comments on commit 87c0a66

Please sign in to comment.