diff --git a/shared-infrastructure b/shared-infrastructure
index a75469fdb..36b2d55f5 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f
+Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5
diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index d810296d6..a988e22b2 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Advanced
=> GetConfiguration((IConfigurationProvider)source);
///
- /// Gets the configuration .
+ /// Gets the configuration.
///
/// The source image
/// Returns the bounds of the image
@@ -48,15 +49,58 @@ namespace SixLabors.ImageSharp.Advanced
=> source?.Configuration ?? Configuration.Default;
///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
+ /// Gets the representation of the pixels as a containing the backing pixel data of the image
+ /// stored in row major order, as a list of contiguous blocks in the source image's pixel format.
///
+ /// The source image.
/// The type of the pixel.
- /// The source.
+ /// The .
+ ///
+ /// Certain Image Processors may invalidate the returned and all it's buffers,
+ /// therefore it's not recommended to mutate the image while holding a reference to it's .
+ ///
+ public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source)
+ where TPixel : struct, IPixel
+ => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source));
+
+ ///
+ /// Gets the representation of the pixels as a containing the backing pixel data of the image
+ /// stored in row major order, as a list of contiguous blocks in the source image's pixel format.
+ ///
+ /// The source image.
+ /// The type of the pixel.
+ /// The .
+ ///
+ /// Certain Image Processors may invalidate the returned and all it's buffers,
+ /// therefore it's not recommended to mutate the image while holding a reference to it's .
+ ///
+ public static IMemoryGroup GetPixelMemoryGroup(this Image source)
+ where TPixel : struct, IPixel
+ => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
+
+ ///
+ /// Gets the representation of the pixels as a in the source image's pixel format
+ /// stored in row major order, if the backing buffer is contiguous.
+ ///
+ /// The type of the pixel.
+ /// The source image.
/// The
+ /// Thrown when the backing buffer is discontiguous.
+ [Obsolete(
+ @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span GetPixelSpan(this ImageFrame source)
where TPixel : struct, IPixel
- => source.GetPixelMemory().Span;
+ {
+ Guard.NotNull(source, nameof(source));
+
+ IMemoryGroup 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;
+ }
///
/// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
@@ -65,9 +109,16 @@ namespace SixLabors.ImageSharp.Advanced
/// The type of the pixel.
/// The source.
/// The
+ /// Thrown when the backing buffer is discontiguous.
+ [Obsolete(
+ @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span GetPixelSpan(this Image source)
where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelSpan();
+ {
+ Guard.NotNull(source, nameof(source));
+
+ return source.Frames.RootFrame.GetPixelSpan();
+ }
///
/// Gets the representation of the pixels as a of contiguous memory
@@ -79,7 +130,13 @@ namespace SixLabors.ImageSharp.Advanced
/// The
public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex)
where TPixel : struct, IPixel
- => source.PixelBuffer.GetRowSpan(rowIndex);
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.PixelBuffer.GetRowSpan(rowIndex);
+ }
///
/// Gets the representation of the pixels as of of contiguous memory
@@ -91,58 +148,12 @@ namespace SixLabors.ImageSharp.Advanced
/// The
public static Span GetPixelRowSpan(this Image source, int rowIndex)
where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelRowSpan(rowIndex);
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer,
- /// allowing direct manipulation of pixel data through unsafe operations.
- /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
- ///
- /// The Pixel format.
- /// The source image frame
- /// A pinnable reference the first root of the pixel buffer.
- [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
- public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source)
- where TPixel : struct, IPixel
- => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source);
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer,
- /// allowing direct manipulation of pixel data through unsafe operations.
- /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
- ///
- /// The Pixel format.
- /// The source image
- /// A pinnable reference the first root of the pixel buffer.
- [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
- public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source)
- where TPixel : struct, IPixel
- => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
-
- ///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
- ///
- /// The Pixel format.
- /// The source
- /// The
- internal static Memory GetPixelMemory(this ImageFrame source)
- where TPixel : struct, IPixel
{
- return source.PixelBuffer.MemorySource.Memory;
- }
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
- ///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
- ///
- /// The Pixel format.
- /// The source
- /// The
- internal static Memory GetPixelMemory(this Image source)
- where TPixel : struct, IPixel
- {
- return source.Frames.RootFrame.GetPixelMemory();
+ return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}
///
@@ -153,9 +164,15 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex)
+ public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex)
where TPixel : struct, IPixel
- => source.PixelBuffer.GetRowMemory(rowIndex);
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.PixelBuffer.GetSafeRowMemory(rowIndex);
+ }
///
/// Gets the representation of the pixels as of of contiguous memory
@@ -165,9 +182,15 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- internal static Memory GetPixelRowMemory(this Image source, int rowIndex)
+ public static Memory GetPixelRowMemory(this Image source, int rowIndex)
where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
+ }
///
/// Gets the assigned to 'source'.
@@ -176,15 +199,5 @@ namespace SixLabors.ImageSharp.Advanced
/// Returns the configuration.
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer.
- /// Such a reference can be used for pinning but must never be dereferenced.
- ///
- /// The source image frame
- /// A reference to the element.
- private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source)
- where TPixel : struct, IPixel
- => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
}
}
diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
index 8b9dbe1b8..4028b70b0 100644
--- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
+++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
@@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
{
///
/// The exception that is thrown when the library tries to load
- /// an image, which has an invalid format.
+ /// an image, which has format or content that is invalid or unsupported by ImageSharp.
///
public class ImageFormatException : Exception
{
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 619be880a..47c7c54ea 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp
/// The default value is 1MB.
///
///
- /// Currently only used by Resize.
+ /// Currently only used by Resize. If the working buffer is expected to be discontiguous,
+ /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used.
///
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index a404ab418..e5546b361 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
@@ -1,7 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -32,7 +33,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
Guard.NotNull(stream, nameof(stream));
- return new BmpDecoderCore(configuration, this).Decode(stream);
+ var decoder = new BmpDecoderCore(configuration, this);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ 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);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 8d82d28fb..80b20c025 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.options = options;
}
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
+
///
/// Decodes the image from the specified this._stream and sets
/// the data to image.
@@ -294,24 +299,27 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel
{
TPixel color = default;
- using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
- using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
+ using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean))
{
Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
- if (compression == BmpCompression.RLE8)
+ Span undefinedPixelsSpan = undefinedPixels.Memory.Span;
+ Span bufferSpan = buffer.Memory.Span;
+ if (compression is BmpCompression.RLE8)
{
- this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+ this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
- this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+ this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
- Span bufferRow = buffer.GetRowSpan(y);
+ int rowStartIdx = y * width;
+ Span bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span pixelRow = pixels.GetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
@@ -321,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int x = 0; x < width; x++)
{
byte colorIdx = bufferRow[x];
- if (undefinedPixels[x, y])
+ if (undefinedPixelsSpan[rowStartIdx + x])
{
switch (this.options.RleSkippedPixelHandling)
{
@@ -372,12 +380,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
TPixel color = default;
using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean))
- using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean))
{
Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
+ Span undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span bufferSpan = buffer.GetSpan();
- this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+
+ this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
@@ -386,11 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
- int rowStartIdx = y * width * 3;
+ var yMulWidth = y * width;
+ int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + (x * 3);
- if (undefinedPixels[x, y])
+ if (undefinedPixelsSpan[yMulWidth + x])
{
switch (this.options.RleSkippedPixelHandling)
{
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 7691ec1aa..24e3d8826 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -27,7 +29,19 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : struct, IPixel
{
var decoder = new GifDecoderCore(configuration, this);
- return decoder.Decode(stream);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 98dbddb48..bc508cba7 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
public bool IgnoreMetadata { get; internal set; }
///
- /// Gets the decoding mode for multi-frame images
+ /// Gets the decoding mode for multi-frame images.
///
public FrameDecodingMode DecodingMode { get; }
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height);
+
private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator;
///
diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs
index e8e84de7d..7188b57a6 100644
--- a/src/ImageSharp/Formats/IImageDecoder.cs
+++ b/src/ImageSharp/Formats/IImageDecoder.cs
@@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Formats
/// The configuration for the image.
/// The containing image data.
/// The .
+ // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream)
where TPixel : struct, IPixel;
@@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats
/// The configuration for the image.
/// The containing image data.
/// The .
+ // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
index 6bf9c8483..64d1d68b7 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
@@ -139,4 +139,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
index 39c8be312..22bc8ccaa 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
@@ -31,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.Component = component;
this.ImagePostProcessor = imagePostProcessor;
- this.ColorBuffer = memoryAllocator.Allocate2D(
+ this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
+ this.ColorBuffer = memoryAllocator.Allocate2DOveraligned(
imagePostProcessor.PostProcessorBufferSize.Width,
- imagePostProcessor.PostProcessorBufferSize.Height);
+ imagePostProcessor.PostProcessorBufferSize.Height,
+ this.blockAreaSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
- this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
}
///
@@ -111,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
index 92482de2a..9619a78fc 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
@@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , )
///
- public void Convert(ImageFrame frame, int x, int y)
+ public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows)
{
- this.pixelBlock.LoadAndStretchEdges(frame, x, y);
+ this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
Span rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
index 3d1e22a99..534c66b99 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
@@ -54,24 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
- public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY)
- where TPixel : struct, IPixel
- {
- if (source.PixelBuffer is Buffer2D buffer)
- {
- this.LoadAndStretchEdges(buffer, sourceX, sourceY);
- }
- else
- {
- throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !");
- }
- }
-
///
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
///
- public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY)
+ public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
@@ -85,15 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As, byte>(ref this);
- ref byte imageStart = ref Unsafe.As(
- ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX));
-
int blockRowSizeInBytes = 8 * Unsafe.SizeOf();
- int imageRowSizeInBytes = source.Width * Unsafe.SizeOf();
for (int y = 0; y < height; y++)
{
- ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
+ Span row = currentRows[y];
+
+ ref byte s = ref Unsafe.As(ref row[sourceX]);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
@@ -127,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
new file mode 100644
index 000000000..8c3daa4d5
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components
+{
+ ///
+ /// Cache 8 pixel rows on the stack, which may originate from different buffers of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal readonly ref struct RowOctet
+ where T : struct
+ {
+ private readonly Span row0;
+ private readonly Span row1;
+ private readonly Span row2;
+ private readonly Span row3;
+ private readonly Span row4;
+ private readonly Span row5;
+ private readonly Span row6;
+ private readonly Span row7;
+
+ public RowOctet(Buffer2D buffer, int startY)
+ {
+ int y = startY;
+ int height = buffer.Height;
+ this.row0 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row1 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row2 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row3 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row4 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row5 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row6 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row7 = y < height ? buffer.GetRowSpan(y) : default;
+ }
+
+ public Span this[int y]
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get
+ {
+ // No unsafe tricks, since Span can't be used as a generic argument
+ return y switch
+ {
+ 0 => this.row0,
+ 1 => this.row1,
+ 2 => this.row2,
+ 3 => this.row3,
+ 4 => this.row4,
+ 5 => this.row5,
+ 6 => this.row6,
+ 7 => this.row7,
+ _ => ThrowIndexOutOfRangeException()
+ };
+ }
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static Span ThrowIndexOutOfRangeException()
+ {
+ throw new IndexOutOfRangeException();
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 4e1c0c1be..31085dbaa 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -22,10 +23,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
- using (var decoder = new JpegDecoderCore(configuration, this))
+ using var decoder = new JpegDecoderCore(configuration, this);
+ try
{
return decoder.Decode(stream);
}
+ catch (InvalidMemoryOperationException ex)
+ {
+ (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index cd3c19aa3..dcf2d72a5 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
var pixelConverter = YCbCrForwardConverter.Create();
+ ImageFrame frame = pixels.Frames.RootFrame;
+ Buffer2D pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 8)
{
+ var currentRows = new RowOctet(pixelBuffer, y);
+
for (int x = 0; x < pixels.Width; x += 8)
{
- pixelConverter.Convert(pixels.Frames.RootFrame, x, y);
+ pixelConverter.Convert(frame, x, y, currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
@@ -935,6 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
+ ImageFrame frame = pixels.Frames.RootFrame;
+ Buffer2D pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 16)
{
@@ -945,7 +952,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
- pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff);
+ // TODO: Try pushing this to the outer loop!
+ var currentRows = new RowOctet(pixelBuffer, y + yOff);
+
+ pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;
diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index eea9e54c0..3b41cfc6e 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@@ -44,7 +45,19 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : struct, IPixel
{
var decoder = new PngDecoderCore(configuration, this);
- return decoder.Decode(stream);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 69b341c8d..2701bd2a7 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private int currentRow = Adam7.FirstRow[0];
///
- /// The current number of bytes read in the current scanline
+ /// The current number of bytes read in the current scanline.
///
private int currentRowBytesRead;
@@ -132,18 +132,23 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ignoreMetadata = options.IgnoreMetadata;
}
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.header.Width, this.header.Height);
+
///
/// Decodes the stream to the image.
///
/// The pixel format.
- /// The stream containing image data.
+ /// The stream containing image data.
///
/// Thrown if the stream does not contain and end chunk.
///
///
/// Thrown if the image is larger than the maximum allowable size.
///
- /// The decoded image
+ /// The decoded image.
public Image Decode(Stream stream)
where TPixel : struct, IPixel
{
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
index b97388773..a6de902b8 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
@@ -17,7 +19,20 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
Guard.NotNull(stream, nameof(stream));
- return new TgaDecoderCore(configuration, this).Decode(stream);
+ var decoder = new TgaDecoderCore(configuration, this);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index 91cc93e19..a86fd3bce 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -61,6 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.options = options;
}
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
+
///
/// Decodes the image from the specified stream.
///
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
index a4b141f38..d1ec2ed4c 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
if (this.compression is TgaCompression.RunLength)
{
- this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
+ this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
}
else
{
@@ -150,19 +150,20 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The pixel type.
/// The stream to write the image to.
/// The image to encode.
- private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image)
+ private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image)
where TPixel : struct, IPixel
{
Rgba32 color = default;
Buffer2D pixels = image.PixelBuffer;
- Span pixelSpan = pixels.GetSpan();
int totalPixels = image.Width * image.Height;
int encodedPixels = 0;
while (encodedPixels < totalPixels)
{
- TPixel currentPixel = pixelSpan[encodedPixels];
+ int x = encodedPixels % pixels.Width;
+ int y = encodedPixels / pixels.Width;
+ TPixel currentPixel = pixels[x, y];
currentPixel.ToRgba32(ref color);
- byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
+ byte equalPixelCount = this.FindEqualPixels(pixels, x, y);
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
@@ -200,30 +201,40 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
///
- /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
+ /// Finds consecutive pixels which have the same value.
///
/// The pixel type.
- /// The pixel span to search in.
+ /// The pixels of the image.
+ /// X coordinate to start searching for the same pixels.
+ /// Y coordinate to start searching for the same pixels.
/// The number of equal pixels.
- private byte FindEqualPixels(Span pixelSpan)
+ private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart)
where TPixel : struct, IPixel
{
- int idx = 0;
byte equalPixelCount = 0;
- while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
+ bool firstRow = true;
+ TPixel startPixel = pixels[xStart, yStart];
+ for (int y = yStart; y < pixels.Height; y++)
{
- TPixel currentPixel = pixelSpan[idx];
- TPixel nextPixel = pixelSpan[idx + 1];
- if (currentPixel.Equals(nextPixel))
+ for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++)
{
- equalPixelCount++;
- }
- else
- {
- return equalPixelCount;
+ TPixel nextPixel = pixels[x, y];
+ if (startPixel.Equals(nextPixel))
+ {
+ equalPixelCount++;
+ }
+ else
+ {
+ return equalPixelCount;
+ }
+
+ if (equalPixelCount >= 127)
+ {
+ return equalPixelCount;
+ }
}
- idx++;
+ firstRow = false;
}
return equalPixelCount;
diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs
index e1376b4a2..5c19a4239 100644
--- a/src/ImageSharp/Image.Decode.cs
+++ b/src/ImageSharp/Image.Decode.cs
@@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp
{
Buffer2D uninitializedMemoryBuffer =
configuration.MemoryAllocator.Allocate2D(width, height);
- return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata);
+ return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata);
}
///
diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs
index eb7ce261f..c655a87d0 100644
--- a/src/ImageSharp/Image.LoadPixelData.cs
+++ b/src/ImageSharp/Image.LoadPixelData.cs
@@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@@ -118,10 +119,10 @@ namespace SixLabors.ImageSharp
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image(config, width, height);
-
- data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan());
+ data = data.Slice(0, count);
+ data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);
return image;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs
index 095991b07..9bb40a78b 100644
--- a/src/ImageSharp/Image.WrapMemory.cs
+++ b/src/ImageSharp/Image.WrapMemory.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel
{
- var memorySource = new MemorySource(pixelMemory);
+ var memorySource = MemoryGroup.Wrap(pixelMemory);
return new Image(config, memorySource, width, height, metadata);
}
@@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp
ImageMetadata metadata)
where TPixel : struct, IPixel
{
- var memorySource = new MemorySource(pixelMemoryOwner, false);
+ var memorySource = MemoryGroup.Wrap(pixelMemoryOwner);
return new Image(config, memorySource, width, height, metadata);
}
@@ -147,4 +147,4 @@ namespace SixLabors.ImageSharp
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs
index 9e90aeaf5..837305d62 100644
--- a/src/ImageSharp/ImageFrame.LoadPixelData.cs
+++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs
@@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp
var image = new ImageFrame(configuration, width, height);
- data.Slice(0, count).CopyTo(image.GetPixelSpan());
+ data = data.Slice(0, count);
+ data.CopyTo(image.PixelBuffer.FastMemoryGroup);
return image;
}
diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs
index 235840e77..cbd526662 100644
--- a/src/ImageSharp/ImageFrame.cs
+++ b/src/ImageSharp/ImageFrame.cs
@@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp
/// Whether to dispose of managed and unmanaged objects.
protected abstract void Dispose(bool disposing);
- internal abstract void CopyPixelsTo(Span destination)
+ internal abstract void CopyPixelsTo(MemoryGroup destination)
where TDestinationPixel : struct, IPixel;
///
diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
index 722a4ddea..b7f1d1bb6 100644
--- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs
+++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp
this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor));
}
- internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource)
+ internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource)
{
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
@@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp
this.parent.GetConfiguration(),
source.Size(),
source.Metadata.DeepClone());
- source.CopyPixelsTo(result.PixelBuffer.GetSpan());
+ source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup);
return result;
}
}
diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs
index a2de8d671..85488c12d 100644
--- a/src/ImageSharp/ImageFrame{TPixel}.cs
+++ b/src/ImageSharp/ImageFrame{TPixel}.cs
@@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp
/// The width of the image in pixels.
/// The height of the image in pixels.
/// The memory source.
- internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource)
+ internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetadata())
{
}
@@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp
/// The height of the image in pixels.
/// The memory source.
/// The metadata.
- internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata)
+ internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata)
: base(configuration, width, height, metadata)
{
Guard.MustBeGreaterThan(width, 0, nameof(width));
@@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp
Guard.NotNull(source, nameof(source));
this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height);
- source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan());
+ source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup);
}
///
@@ -148,13 +148,22 @@ namespace SixLabors.ImageSharp
/// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.
/// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.
/// The at the specified position.
+ /// Thrown when the provided (x,y) coordinates are outside the image boundary.
public TPixel this[int x, int y]
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => this.PixelBuffer[x, y];
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get
+ {
+ this.VerifyCoords(x, y);
+ return this.PixelBuffer.GetElementUnsafe(x, y);
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- set => this.PixelBuffer[x, y] = value;
+ [MethodImpl(InliningOptions.ShortMethod)]
+ set
+ {
+ this.VerifyCoords(x, y);
+ this.PixelBuffer.GetElementUnsafe(x, y) = value;
+ }
}
///
@@ -177,7 +186,7 @@ namespace SixLabors.ImageSharp
throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target));
}
- this.GetPixelSpan().CopyTo(target.GetSpan());
+ this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup);
}
///
@@ -209,15 +218,22 @@ namespace SixLabors.ImageSharp
this.isDisposed = true;
}
- internal override void CopyPixelsTo(Span destination)
+ internal override void CopyPixelsTo(MemoryGroup destination)
{
if (typeof(TPixel) == typeof(TDestinationPixel))
{
- Span dest1 = MemoryMarshal.Cast(destination);
- this.PixelBuffer.GetSpan().CopyTo(dest1);
+ this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) =>
+ {
+ Span d1 = MemoryMarshal.Cast(d);
+ s.CopyTo(d1);
+ });
+ return;
}
- PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSpan(), destination);
+ this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) =>
+ {
+ PixelOperations.Instance.To(this.GetConfiguration(), s, d);
+ });
}
///
@@ -275,18 +291,38 @@ namespace SixLabors.ImageSharp
/// The value to initialize the bitmap with.
internal void Clear(TPixel value)
{
- Span span = this.GetPixelSpan();
+ MemoryGroup group = this.PixelBuffer.FastMemoryGroup;
if (value.Equals(default))
{
- span.Clear();
+ group.Clear();
}
else
{
- span.Fill(value);
+ group.Fill(value);
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void VerifyCoords(int x, int y)
+ {
+ if (x < 0 || x >= this.Width)
+ {
+ ThrowArgumentOutOfRangeException(nameof(x));
+ }
+
+ if (y < 0 || y >= this.Height)
+ {
+ ThrowArgumentOutOfRangeException(nameof(y));
}
}
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static void ThrowArgumentOutOfRangeException(string paramName)
+ {
+ throw new ArgumentOutOfRangeException(paramName);
+ }
+
///
/// A implementing the clone logic for .
///
diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings
index 018ca75cd..6896e069c 100644
--- a/src/ImageSharp/ImageSharp.csproj.DotSettings
+++ b/src/ImageSharp/ImageSharp.csproj.DotSettings
@@ -2,6 +2,8 @@
True
True
True
+ True
+ True
True
True
True
diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs
index 87bdf90a1..83be52dd6 100644
--- a/src/ImageSharp/Image{TPixel}.cs
+++ b/src/ImageSharp/Image{TPixel}.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
@@ -74,22 +75,22 @@ namespace SixLabors.ImageSharp
///
/// Initializes a new instance of the class
- /// wrapping an external .
+ /// wrapping an external .
///
/// The configuration providing initialization code which allows extending the library.
- /// The memory source.
+ /// The memory source.
/// The width of the image in pixels.
/// The height of the image in pixels.
/// The images metadata.
internal Image(
Configuration configuration,
- MemorySource memorySource,
+ MemoryGroup memoryGroup,
int width,
int height,
ImageMetadata metadata)
: base(configuration, PixelTypeInfo.Create(), metadata, width, height)
{
- this.Frames = new ImageFrameCollection(this, width, height, memorySource);
+ this.Frames = new ImageFrameCollection(this, width, height, memoryGroup);
}
///
@@ -144,10 +145,22 @@ namespace SixLabors.ImageSharp
/// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.
/// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.
/// The at the specified position.
+ /// Thrown when the provided (x,y) coordinates are outside the image boundary.
public TPixel this[int x, int y]
{
- get => this.PixelSource.PixelBuffer[x, y];
- set => this.PixelSource.PixelBuffer[x, y] = value;
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get
+ {
+ this.VerifyCoords(x, y);
+ return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ set
+ {
+ this.VerifyCoords(x, y);
+ this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value;
+ }
}
///
@@ -265,5 +278,25 @@ namespace SixLabors.ImageSharp
return rootSize;
}
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void VerifyCoords(int x, int y)
+ {
+ if (x < 0 || x >= this.Width)
+ {
+ ThrowArgumentOutOfRangeException(nameof(x));
+ }
+
+ if (y < 0 || y >= this.Height)
+ {
+ ThrowArgumentOutOfRangeException(nameof(y));
+ }
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static void ThrowArgumentOutOfRangeException(string paramName)
+ {
+ throw new ArgumentOutOfRangeException(paramName);
+ }
}
}
diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
index 0d7e0b784..7a8b4f8bd 100644
--- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
+++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
@@ -46,7 +46,15 @@ namespace SixLabors.ImageSharp.Memory
protected byte[] Data { get; private set; }
///
- public override Span GetSpan() => MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length);
+ public override Span GetSpan()
+ {
+ if (this.Data == null)
+ {
+ throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer");
+ }
+
+ return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length);
+ }
///
protected override void Dispose(bool disposing)
diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
index 1ce2525b8..5ef60c9ed 100644
--- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
+++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
@@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Memory
///
private const int DefaultNormalPoolBucketCount = 16;
+ // TODO: This value should be determined by benchmarking
+ private const int DefaultBufferCapacityInBytes = int.MaxValue / 4;
+
///
/// This is the default. Should be good for most use cases.
///
@@ -39,7 +42,8 @@ namespace SixLabors.ImageSharp.Memory
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
- DefaultNormalPoolBucketCount);
+ DefaultNormalPoolBucketCount,
+ DefaultBufferCapacityInBytes);
}
///
@@ -69,4 +73,4 @@ namespace SixLabors.ImageSharp.Memory
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
index c4d92ca3c..8043c1888 100644
--- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
@@ -60,13 +60,41 @@ namespace SixLabors.ImageSharp.Memory
/// The threshold to pool arrays in which has less buckets for memory safety.
/// Max arrays per bucket for the large array pool.
/// Max arrays per bucket for the normal array pool.
- public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool)
+ public ArrayPoolMemoryAllocator(
+ int maxPoolSizeInBytes,
+ int poolSelectorThresholdInBytes,
+ int maxArraysPerBucketLargePool,
+ int maxArraysPerBucketNormalPool)
+ : this(
+ maxPoolSizeInBytes,
+ poolSelectorThresholdInBytes,
+ maxArraysPerBucketLargePool,
+ maxArraysPerBucketNormalPool,
+ DefaultBufferCapacityInBytes)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.
+ /// The threshold to pool arrays in which has less buckets for memory safety.
+ /// Max arrays per bucket for the large array pool.
+ /// Max arrays per bucket for the normal array pool.
+ /// The length of the largest contiguous buffer that can be handled by this allocator instance.
+ public ArrayPoolMemoryAllocator(
+ int maxPoolSizeInBytes,
+ int poolSelectorThresholdInBytes,
+ int maxArraysPerBucketLargePool,
+ int maxArraysPerBucketNormalPool,
+ int bufferCapacityInBytes)
{
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
+ this.BufferCapacityInBytes = bufferCapacityInBytes;
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
@@ -83,23 +111,30 @@ namespace SixLabors.ImageSharp.Memory
///
public int PoolSelectorThresholdInBytes { get; }
+ ///
+ /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance.
+ ///
+ public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests
+
///
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
+ ///
+ protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
+
///
public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf();
int bufferSizeInBytes = length * itemSizeBytes;
- if (bufferSizeInBytes < 0)
+ if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes)
{
- throw new ArgumentOutOfRangeException(
- nameof(length),
- $"{nameof(ArrayPoolMemoryAllocator)} can not allocate {length} elements of {typeof(T).Name}.");
+ throw new InvalidMemoryOperationException(
+ $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator.");
}
ArrayPool pool = this.GetArrayPool(bufferSizeInBytes);
@@ -147,4 +182,4 @@ namespace SixLabors.ImageSharp.Memory
this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
index 20598c3e3..a4e1de197 100644
--- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
@@ -10,6 +11,12 @@ namespace SixLabors.ImageSharp.Memory
///
public abstract class MemoryAllocator
{
+ ///
+ /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
+ ///
+ /// The length of the largest contiguous buffer that can be handled by this allocator instance.
+ protected internal abstract int GetBufferCapacityInBytes();
+
///
/// Allocates an , holding a of length .
///
@@ -17,6 +24,8 @@ namespace SixLabors.ImageSharp.Memory
/// Size of the buffer to allocate.
/// The allocation options.
/// A buffer of values of type .
+ /// When length is zero or negative.
+ /// When length is over the capacity of the allocator.
public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
where T : struct;
@@ -26,6 +35,8 @@ namespace SixLabors.ImageSharp.Memory
/// The requested buffer length.
/// The allocation options.
/// The .
+ /// When length is zero or negative.
+ /// When length is over the capacity of the allocator.
public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None);
///
diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
index 54b64b131..4c62e4ded 100644
--- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
+++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
@@ -7,10 +7,13 @@ using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
///
- /// Implements by newing up arrays by the GC on every allocation requests.
+ /// Implements by newing up managed arrays on every allocation request.
///
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
+ ///
+ protected internal override int GetBufferCapacityInBytes() => int.MaxValue;
+
///
public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None)
{
@@ -27,4 +30,4 @@ namespace SixLabors.ImageSharp.Memory
return new BasicByteBuffer(new byte[length]);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index ba4f9c925..8b0f3845e 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -14,62 +15,66 @@ namespace SixLabors.ImageSharp.Memory
public static class Buffer2DExtensions
{
///
- /// Gets a to the backing buffer of .
+ /// Gets the backing .
///
- /// The .
- /// The value type.
- /// The referencing the memory area.
- public static Span GetSpan(this Buffer2D buffer)
+ /// The buffer.
+ /// The element type.
+ /// The MemoryGroup.
+ public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
- return buffer.MemorySource.GetSpan();
+ return buffer.FastMemoryGroup.View;
}
///
- /// Gets the holding the backing buffer of .
+ /// Gets a to the backing data of
+ /// if the backing group consists of one single contiguous memory buffer.
+ /// Throws otherwise.
///
/// The .
/// The value type.
- /// The .
- public static Memory GetMemory(this Buffer2D buffer)
+ /// The referencing the memory area.
+ ///
+ /// Thrown when the backing group is discontiguous.
+ ///
+ internal static Span GetSingleSpan(this Buffer2D buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
- return buffer.MemorySource.Memory;
- }
+ if (buffer.FastMemoryGroup.Count > 1)
+ {
+ throw new InvalidOperationException("GetSingleSpan is only valid for a single-buffer group!");
+ }
- ///
- /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
- ///
- /// The buffer
- /// The y (row) coordinate
- /// The element type
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Span GetRowSpan(this Buffer2D buffer, int y)
- where T : struct
- {
- Guard.NotNull(buffer, nameof(buffer));
- return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
+ return buffer.FastMemoryGroup.Single().Span;
}
///
- /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ /// Gets a to the backing data of
+ /// if the backing group consists of one single contiguous memory buffer.
+ /// Throws otherwise.
///
- /// The buffer
- /// The y (row) coordinate
- /// The element type
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Memory GetRowMemory(this Buffer2D buffer, int y)
+ /// The .
+ /// The value type.
+ /// The .
+ ///
+ /// Thrown when the backing group is discontiguous.
+ ///
+ internal static Memory GetSingleMemory(this Buffer2D buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
- return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
+ if (buffer.FastMemoryGroup.Count > 1)
+ {
+ throw new InvalidOperationException("GetSingleMemory is only valid for a single-buffer group!");
+ }
+
+ return buffer.FastMemoryGroup.Single();
}
///
+ /// TODO: Does not work with multi-buffer groups, should be specific to Resize.
/// Copy columns of inplace,
/// from positions starting at to positions at .
///
@@ -91,7 +96,7 @@ namespace SixLabors.ImageSharp.Memory
int dOffset = destIndex * elementSize;
long count = columnCount * elementSize;
- Span span = MemoryMarshal.AsBytes(buffer.GetMemory().Span);
+ Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span);
fixed (byte* ptr = span)
{
@@ -145,15 +150,6 @@ namespace SixLabors.ImageSharp.Memory
where T : struct =>
new BufferArea(buffer);
- ///
- /// Gets a span for all the pixels in defined by
- ///
- internal static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows)
- where T : struct
- {
- return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
- }
-
///
/// Returns the size of the buffer.
///
diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs
index 6b7f3bf42..f22b9a875 100644
--- a/src/ImageSharp/Memory/Buffer2D{T}.cs
+++ b/src/ImageSharp/Memory/Buffer2D{T}.cs
@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Memory
{
@@ -14,23 +15,27 @@ namespace SixLabors.ImageSharp.Memory
/// Before RC1, this class might be target of API changes, use it on your own risk!
///
/// The value type.
- // TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core).
public sealed class Buffer2D : IDisposable
where T : struct
{
- private MemorySource memorySource;
+ private Memory cachedMemory = default;
///
/// Initializes a new instance of the class.
///
- /// The buffer to wrap
- /// The number of elements in a row
- /// The number of rows
- internal Buffer2D(MemorySource memorySource, int width, int height)
+ /// The to wrap.
+ /// The number of elements in a row.
+ /// The number of rows.
+ internal Buffer2D(MemoryGroup memoryGroup, int width, int height)
{
- this.memorySource = memorySource;
+ this.FastMemoryGroup = memoryGroup;
this.Width = width;
this.Height = height;
+
+ if (memoryGroup.Count == 1)
+ {
+ this.cachedMemory = memoryGroup[0];
+ }
}
///
@@ -44,9 +49,20 @@ namespace SixLabors.ImageSharp.Memory
public int Height { get; private set; }
///
- /// Gets the backing
+ /// Gets the backing .
///
- internal MemorySource MemorySource => this.memorySource;
+ /// The MemoryGroup.
+ public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View;
+
+ ///
+ /// Gets the backing without the view abstraction.
+ ///
+ ///
+ /// This property has been kept internal intentionally.
+ /// It's public counterpart is ,
+ /// which only exposes the view of the MemoryGroup.
+ ///
+ internal MemoryGroup FastMemoryGroup { get; }
///
/// Gets a reference to the element at the specified position.
@@ -54,16 +70,18 @@ namespace SixLabors.ImageSharp.Memory
/// The x coordinate (row)
/// The y coordinate (position at row)
/// A reference to the element.
- internal ref T this[int x, int y]
+ /// When index is out of range of the buffer.
+ public ref T this[int x, int y]
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
+ DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x));
+ DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
- Span span = this.GetSpan();
- return ref span[(this.Width * y) + x];
+ return ref this.GetRowSpan(y)[x];
}
}
@@ -72,7 +90,72 @@ namespace SixLabors.ImageSharp.Memory
///
public void Dispose()
{
- this.MemorySource.Dispose();
+ this.FastMemoryGroup.Dispose();
+ this.cachedMemory = default;
+ }
+
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ ///
+ ///
+ /// This method does not validate the y argument for performance reason,
+ /// is being propagated from lower levels.
+ ///
+ /// The row index.
+ /// The of the pixels in the row.
+ /// Thrown when row index is out of range.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Span GetRowSpan(int y)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
+ DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
+
+ return this.cachedMemory.Length > 0
+ ? this.cachedMemory.Span.Slice(y * this.Width, this.Width)
+ : this.GetRowMemorySlow(y).Span;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal ref T GetElementUnsafe(int x, int y)
+ {
+ if (this.cachedMemory.Length > 0)
+ {
+ Span span = this.cachedMemory.Span;
+ ref T start = ref MemoryMarshal.GetReference(span);
+ return ref Unsafe.Add(ref start, (y * this.Width) + x);
+ }
+
+ return ref this.GetElementSlow(x, y);
+ }
+
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ /// This method is intended for internal use only, since it does not use the indirection provided by
+ /// .
+ ///
+ /// The y (row) coordinate.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal Memory GetFastRowMemory(int y)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
+ DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
+ return this.cachedMemory.Length > 0
+ ? this.cachedMemory.Slice(y * this.Width, this.Width)
+ : this.GetRowMemorySlow(y);
+ }
+
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row.
+ ///
+ /// The y (row) coordinate.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal Memory GetSafeRowMemory(int y)
+ {
+ DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
+ DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
+ return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width);
}
///
@@ -81,11 +164,21 @@ namespace SixLabors.ImageSharp.Memory
///
internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source)
{
- MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
- SwapDimensionData(destination, source);
+ bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup);
+ SwapOwnData(destination, source, swap);
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width);
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private ref T GetElementSlow(int x, int y)
+ {
+ Span span = this.GetRowMemorySlow(y).Span;
+ return ref span[x];
}
- private static void SwapDimensionData(Buffer2D a, Buffer2D b)
+ private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory)
{
Size aSize = a.Size();
Size bSize = b.Size();
@@ -95,6 +188,13 @@ namespace SixLabors.ImageSharp.Memory
a.Width = bSize.Width;
a.Height = bSize.Height;
+
+ if (swapCachedMemory)
+ {
+ Memory aCached = a.cachedMemory;
+ a.cachedMemory = b.cachedMemory;
+ b.cachedMemory = aCached;
+ }
}
}
}
diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs
index 08731846e..f5cbc6953 100644
--- a/src/ImageSharp/Memory/BufferArea{T}.cs
+++ b/src/ImageSharp/Memory/BufferArea{T}.cs
@@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory
/// Represents a rectangular area inside a 2D memory buffer ().
/// This type is kind-of 2D Span, but it can live on heap.
///
- /// The element type
+ /// The element type.
internal readonly struct BufferArea
where T : struct
{
@@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Memory
/// The position inside a row
/// The row index
/// The reference to the value
- public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)];
+ public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y];
///
/// Gets a reference to the [0,0] element.
@@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Memory
/// The reference to the [0,0] element
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigin() =>
- ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X];
+ ref this.GetRowSpan(0)[0];
///
/// Gets a span to row 'y' inside this area.
@@ -94,16 +94,16 @@ namespace SixLabors.ImageSharp.Memory
int xx = this.Rectangle.X;
int width = this.Rectangle.Width;
- return this.DestinationBuffer.GetSpan().Slice(yy + xx, width);
+ return this.DestinationBuffer.FastMemoryGroup.GetBoundedSlice(yy + xx, width).Span;
}
///
/// Returns a sub-area as . (Similar to .)
///
- /// The x index at the subarea origo
- /// The y index at the subarea origo
- /// The desired width of the subarea
- /// The desired height of the subarea
+ /// The x index at the subarea origin.
+ /// The y index at the subarea origin.
+ /// The desired width of the subarea.
+ /// The desired height of the subarea.
/// The subarea
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea GetSubArea(int x, int y, int width, int height)
@@ -129,14 +129,6 @@ namespace SixLabors.ImageSharp.Memory
return new BufferArea(this.DestinationBuffer, rectangle);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int GetIndexOf(int x, int y)
- {
- int yy = this.GetRowIndex(y);
- int xx = this.Rectangle.X + x;
- return yy + xx;
- }
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int GetRowIndex(int y)
{
@@ -148,7 +140,7 @@ namespace SixLabors.ImageSharp.Memory
// Optimization for when the size of the area is the same as the buffer size.
if (this.IsFullBufferArea)
{
- this.DestinationBuffer.GetSpan().Clear();
+ this.DestinationBuffer.FastMemoryGroup.Clear();
return;
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
new file mode 100644
index 000000000..2649b7fb1
--- /dev/null
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// Represents discontiguous group of multiple uniformly-sized memory segments.
+ /// The last segment can be smaller than the preceding ones.
+ ///
+ /// The element type.
+ public interface IMemoryGroup : IReadOnlyList>
+ where T : struct
+ {
+ ///
+ /// Gets the number of elements per contiguous sub-buffer preceding the last buffer.
+ /// The last buffer is allowed to be smaller.
+ ///
+ public int BufferLength { get; }
+
+ ///
+ /// Gets the aggregate number of elements in the group.
+ ///
+ public long TotalLength { get; }
+
+ ///
+ /// Gets a value indicating whether the group has been invalidated.
+ ///
+ ///
+ /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces
+ /// the image buffers internally.
+ ///
+ bool IsValid { get; }
+ }
+}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
new file mode 100644
index 000000000..28da49263
--- /dev/null
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ internal static class MemoryGroupExtensions
+ {
+ internal static void Fill(this IMemoryGroup group, T value)
+ where T : struct
+ {
+ foreach (Memory memory in group)
+ {
+ memory.Span.Fill(value);
+ }
+ }
+
+ internal static void Clear(this IMemoryGroup group)
+ where T : struct
+ {
+ foreach (Memory memory in group)
+ {
+ memory.Span.Clear();
+ }
+ }
+
+ ///
+ /// Returns a slice that is expected to be within the bounds of a single buffer.
+ /// Otherwise is thrown.
+ ///
+ internal static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length)
+ where T : struct
+ {
+ Guard.NotNull(group, nameof(group));
+ Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!");
+ Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
+ Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
+
+ int bufferIdx = (int)(start / group.BufferLength);
+ if (bufferIdx >= group.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start));
+ }
+
+ int bufferStart = (int)(start % group.BufferLength);
+ int bufferEnd = bufferStart + length;
+ Memory memory = group[bufferIdx];
+
+ if (bufferEnd > memory.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return memory.Slice(bufferStart, length);
+ }
+
+ internal static void CopyTo(this IMemoryGroup source, Span target)
+ where T : struct
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target));
+
+ var cur = new MemoryGroupCursor(source);
+ long position = 0;
+ while (position < source.TotalLength)
+ {
+ int fwd = Math.Min(cur.LookAhead(), target.Length);
+ cur.GetSpan(fwd).CopyTo(target);
+
+ cur.Forward(fwd);
+ target = target.Slice(fwd);
+ position += fwd;
+ }
+ }
+
+ internal static void CopyTo(this Span source, IMemoryGroup