diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 68f4e5fa2d..c45450a47b 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -317,7 +317,7 @@ internal sealed class GifDecoderCore : ImageDecoderCore
bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.skipMetadata)
{
- GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
+ GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream);
if (extension.Data.Length > 0)
{
this.metadata!.XmpProfile = new XmpProfile(extension.Data);
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
index 1c1127c3be..8bd8497eea 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif;
@@ -26,11 +25,10 @@ internal readonly struct GifXmpApplicationExtension : IGifExtension
/// Reads the XMP metadata from the specified stream.
///
/// The stream to read from.
- /// The memory allocator.
/// The XMP metadata
- public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator)
+ public static GifXmpApplicationExtension Read(Stream stream)
{
- byte[] xmpBytes = ReadXmpData(stream, allocator);
+ byte[] xmpBytes = ReadXmpData(stream);
// Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF
int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0
@@ -71,9 +69,9 @@ internal readonly struct GifXmpApplicationExtension : IGifExtension
return this.ContentLength;
}
- private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator)
+ private static byte[] ReadXmpData(Stream stream)
{
- using ChunkedMemoryStream bytes = new(allocator);
+ using MemoryStream bytes = new();
// XMP data doesn't have a fixed length nor is there an indicator of the length.
// So we simply read one byte at a time until we hit the 0x0 value at the end
diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs
index 549a28d409..03cfa27cfb 100644
--- a/src/ImageSharp/Formats/ImageDecoder.cs
+++ b/src/ImageSharp/Formats/ImageDecoder.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -210,7 +209,7 @@ public abstract class ImageDecoder : IImageDecoder
}
Configuration configuration = options.Configuration;
- using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
+ using MemoryStream memoryStream = new();
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0;
@@ -266,11 +265,6 @@ public abstract class ImageDecoder : IImageDecoder
return PerformActionAndResetPosition(ms, ms.Position, cancellationToken);
}
- if (stream is ChunkedMemoryStream cms)
- {
- return PerformActionAndResetPosition(cms, cms.Position, cancellationToken);
- }
-
return CopyToMemoryStreamAndActionAsync(options, stream, PerformActionAndResetPosition, cancellationToken);
}
@@ -282,9 +276,11 @@ public abstract class ImageDecoder : IImageDecoder
{
long position = stream.CanSeek ? stream.Position : 0;
Configuration configuration = options.Configuration;
- await using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
+
+ await using MemoryStream memoryStream = new();
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
+
return await action(memoryStream, position, cancellationToken).ConfigureAwait(false);
}
diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs
index deb527f698..34d34c3637 100644
--- a/src/ImageSharp/Formats/ImageEncoder.cs
+++ b/src/ImageSharp/Formats/ImageEncoder.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
@@ -48,8 +47,8 @@ public abstract class ImageEncoder : IImageEncoder
}
else
{
- using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
- this.Encode(image, stream, cancellationToken);
+ using MemoryStream ms = new();
+ this.Encode(image, ms, cancellationToken);
ms.Position = 0;
ms.CopyTo(stream, configuration.StreamProcessingBufferSize);
}
@@ -65,7 +64,7 @@ public abstract class ImageEncoder : IImageEncoder
}
else
{
- using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
+ await using MemoryStream ms = new();
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs
deleted file mode 100644
index 2534548141..0000000000
--- a/src/ImageSharp/IO/ChunkedMemoryStream.cs
+++ /dev/null
@@ -1,585 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.IO;
-
-///
-/// Provides an in-memory stream composed of non-contiguous chunks that doesn't need to be resized.
-/// Chunks are allocated by the assigned via the constructor
-/// and is designed to take advantage of buffer pooling when available.
-///
-internal sealed class ChunkedMemoryStream : Stream
-{
- // The memory allocator.
- private readonly MemoryAllocator allocator;
-
- // Data
- private MemoryChunk? memoryChunk;
-
- // The total number of allocated chunks
- private int chunkCount;
-
- // The length of the largest contiguous buffer that can be handled by the allocator.
- private readonly int allocatorCapacity;
-
- // Has the stream been disposed.
- private bool isDisposed;
-
- // Current chunk to write to
- private MemoryChunk? writeChunk;
-
- // Offset into chunk to write to
- private int writeOffset;
-
- // Current chunk to read from
- private MemoryChunk? readChunk;
-
- // Offset into chunk to read from
- private int readOffset;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator.
- public ChunkedMemoryStream(MemoryAllocator allocator)
- {
- this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
- this.allocator = allocator;
- }
-
- ///
- public override bool CanRead => !this.isDisposed;
-
- ///
- public override bool CanSeek => !this.isDisposed;
-
- ///
- public override bool CanWrite => !this.isDisposed;
-
- ///
- public override long Length
- {
- get
- {
- this.EnsureNotDisposed();
-
- int length = 0;
- MemoryChunk? chunk = this.memoryChunk;
- while (chunk != null)
- {
- MemoryChunk? next = chunk.Next;
- if (next != null)
- {
- length += chunk.Length;
- }
- else
- {
- length += this.writeOffset;
- }
-
- chunk = next;
- }
-
- return length;
- }
- }
-
- ///
- public override long Position
- {
- get
- {
- this.EnsureNotDisposed();
-
- if (this.readChunk is null)
- {
- return 0;
- }
-
- int pos = 0;
- MemoryChunk? chunk = this.memoryChunk;
- while (chunk != this.readChunk && chunk is not null)
- {
- pos += chunk.Length;
- chunk = chunk.Next;
- }
-
- pos += this.readOffset;
-
- return pos;
- }
-
- set
- {
- this.EnsureNotDisposed();
-
- if (value < 0)
- {
- ThrowArgumentOutOfRange(nameof(value));
- }
-
- // Back up current position in case new position is out of range
- MemoryChunk? backupReadChunk = this.readChunk;
- int backupReadOffset = this.readOffset;
-
- this.readChunk = null;
- this.readOffset = 0;
-
- int leftUntilAtPos = (int)value;
- MemoryChunk? chunk = this.memoryChunk;
- while (chunk != null)
- {
- if ((leftUntilAtPos < chunk.Length)
- || ((leftUntilAtPos == chunk.Length)
- && (chunk.Next is null)))
- {
- // The desired position is in this chunk
- this.readChunk = chunk;
- this.readOffset = leftUntilAtPos;
- break;
- }
-
- leftUntilAtPos -= chunk.Length;
- chunk = chunk.Next;
- }
-
- if (this.readChunk is null)
- {
- // Position is out of range
- this.readChunk = backupReadChunk;
- this.readOffset = backupReadOffset;
- }
- }
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override long Seek(long offset, SeekOrigin origin)
- {
- this.EnsureNotDisposed();
-
- switch (origin)
- {
- case SeekOrigin.Begin:
- this.Position = offset;
- break;
-
- case SeekOrigin.Current:
- this.Position += offset;
- break;
-
- case SeekOrigin.End:
- this.Position = this.Length + offset;
- break;
- default:
- ThrowInvalidSeek();
- break;
- }
-
- return this.Position;
- }
-
- ///
- public override void SetLength(long value)
- => throw new NotSupportedException();
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (this.isDisposed)
- {
- return;
- }
-
- try
- {
- this.isDisposed = true;
- if (disposing)
- {
- ReleaseMemoryChunks(this.memoryChunk);
- }
-
- this.memoryChunk = null;
- this.writeChunk = null;
- this.readChunk = null;
- this.chunkCount = 0;
- }
- finally
- {
- base.Dispose(disposing);
- }
- }
-
- ///
- public override void Flush()
- {
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override int Read(byte[] buffer, int offset, int count)
- {
- Guard.NotNull(buffer, nameof(buffer));
- Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset));
- Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count));
-
- const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
- Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
-
- return this.ReadImpl(buffer.AsSpan(offset, count));
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override int Read(Span buffer) => this.ReadImpl(buffer);
-
- private int ReadImpl(Span buffer)
- {
- this.EnsureNotDisposed();
-
- if (this.readChunk is null)
- {
- if (this.memoryChunk is null)
- {
- return 0;
- }
-
- this.readChunk = this.memoryChunk;
- this.readOffset = 0;
- }
-
- IMemoryOwner chunkBuffer = this.readChunk.Buffer;
- int chunkSize = this.readChunk.Length;
- if (this.readChunk.Next is null)
- {
- chunkSize = this.writeOffset;
- }
-
- int bytesRead = 0;
- int offset = 0;
- int count = buffer.Length;
- while (count > 0)
- {
- if (this.readOffset == chunkSize)
- {
- // Exit if no more chunks are currently available
- if (this.readChunk.Next is null)
- {
- break;
- }
-
- this.readChunk = this.readChunk.Next;
- this.readOffset = 0;
- chunkBuffer = this.readChunk.Buffer;
- chunkSize = this.readChunk.Length;
- if (this.readChunk.Next is null)
- {
- chunkSize = this.writeOffset;
- }
- }
-
- int readCount = Math.Min(count, chunkSize - this.readOffset);
- chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer[offset..]);
- offset += readCount;
- count -= readCount;
- this.readOffset += readCount;
- bytesRead += readCount;
- }
-
- return bytesRead;
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override int ReadByte()
- {
- this.EnsureNotDisposed();
-
- if (this.readChunk is null)
- {
- if (this.memoryChunk is null)
- {
- return 0;
- }
-
- this.readChunk = this.memoryChunk;
- this.readOffset = 0;
- }
-
- IMemoryOwner chunkBuffer = this.readChunk.Buffer;
- int chunkSize = this.readChunk.Length;
- if (this.readChunk.Next is null)
- {
- chunkSize = this.writeOffset;
- }
-
- if (this.readOffset == chunkSize)
- {
- // Exit if no more chunks are currently available
- if (this.readChunk.Next is null)
- {
- return -1;
- }
-
- this.readChunk = this.readChunk.Next;
- this.readOffset = 0;
- chunkBuffer = this.readChunk.Buffer;
- }
-
- return chunkBuffer.GetSpan()[this.readOffset++];
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override void Write(byte[] buffer, int offset, int count)
- {
- Guard.NotNull(buffer, nameof(buffer));
- Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset));
- Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count));
-
- const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
- Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
-
- this.WriteImpl(buffer.AsSpan(offset, count));
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override void Write(ReadOnlySpan buffer) => this.WriteImpl(buffer);
-
- private void WriteImpl(ReadOnlySpan buffer)
- {
- this.EnsureNotDisposed();
-
- if (this.memoryChunk is null)
- {
- this.memoryChunk = this.AllocateMemoryChunk();
- this.writeChunk = this.memoryChunk;
- this.writeOffset = 0;
- }
-
- Guard.NotNull(this.writeChunk);
-
- Span chunkBuffer = this.writeChunk.Buffer.GetSpan();
- int chunkSize = this.writeChunk.Length;
- int count = buffer.Length;
- int offset = 0;
- while (count > 0)
- {
- if (this.writeOffset == chunkSize)
- {
- // Allocate a new chunk if the current one is full
- this.writeChunk.Next = this.AllocateMemoryChunk();
- this.writeChunk = this.writeChunk.Next;
- this.writeOffset = 0;
- chunkBuffer = this.writeChunk.Buffer.GetSpan();
- chunkSize = this.writeChunk.Length;
- }
-
- int copyCount = Math.Min(count, chunkSize - this.writeOffset);
- buffer.Slice(offset, copyCount).CopyTo(chunkBuffer[this.writeOffset..]);
-
- offset += copyCount;
- count -= copyCount;
- this.writeOffset += copyCount;
- }
- }
-
- ///
- public override void WriteByte(byte value)
- {
- this.EnsureNotDisposed();
-
- if (this.memoryChunk is null)
- {
- this.memoryChunk = this.AllocateMemoryChunk();
- this.writeChunk = this.memoryChunk;
- this.writeOffset = 0;
- }
-
- Guard.NotNull(this.writeChunk);
-
- IMemoryOwner chunkBuffer = this.writeChunk.Buffer;
- int chunkSize = this.writeChunk.Length;
-
- if (this.writeOffset == chunkSize)
- {
- // Allocate a new chunk if the current one is full
- this.writeChunk.Next = this.AllocateMemoryChunk();
- this.writeChunk = this.writeChunk.Next;
- this.writeOffset = 0;
- chunkBuffer = this.writeChunk.Buffer;
- }
-
- chunkBuffer.GetSpan()[this.writeOffset++] = value;
- }
-
- ///
- /// Copy entire buffer into an array.
- ///
- /// The .
- public byte[] ToArray()
- {
- int length = (int)this.Length; // This will throw if stream is closed
- byte[] copy = new byte[this.Length];
-
- MemoryChunk? backupReadChunk = this.readChunk;
- int backupReadOffset = this.readOffset;
-
- this.readChunk = this.memoryChunk;
- this.readOffset = 0;
- this.Read(copy, 0, length);
-
- this.readChunk = backupReadChunk;
- this.readOffset = backupReadOffset;
-
- return copy;
- }
-
- ///
- /// Write remainder of this stream to another stream.
- ///
- /// The stream to write to.
- public void WriteTo(Stream stream)
- {
- this.EnsureNotDisposed();
-
- Guard.NotNull(stream, nameof(stream));
-
- if (this.readChunk is null)
- {
- if (this.memoryChunk is null)
- {
- return;
- }
-
- this.readChunk = this.memoryChunk;
- this.readOffset = 0;
- }
-
- IMemoryOwner chunkBuffer = this.readChunk.Buffer;
- int chunkSize = this.readChunk.Length;
- if (this.readChunk.Next is null)
- {
- chunkSize = this.writeOffset;
- }
-
- // Following code mirrors Read() logic (readChunk/readOffset should
- // point just past last byte of last chunk when done)
- // loop until end of chunks is found
- while (true)
- {
- if (this.readOffset == chunkSize)
- {
- // Exit if no more chunks are currently available
- if (this.readChunk.Next is null)
- {
- break;
- }
-
- this.readChunk = this.readChunk.Next;
- this.readOffset = 0;
- chunkBuffer = this.readChunk.Buffer;
- chunkSize = this.readChunk.Length;
- if (this.readChunk.Next is null)
- {
- chunkSize = this.writeOffset;
- }
- }
-
- int writeCount = chunkSize - this.readOffset;
- stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount);
- this.readOffset = chunkSize;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void EnsureNotDisposed()
- {
- if (this.isDisposed)
- {
- ThrowDisposed();
- }
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed.");
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value);
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin.");
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private MemoryChunk AllocateMemoryChunk()
- {
- // Tweak our buffer sizes to take the minimum of the provided buffer sizes
- // or the allocator buffer capacity which provides us with the largest
- // available contiguous buffer size.
- IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
-
- return new MemoryChunk(buffer)
- {
- Next = null,
- Length = buffer.Length()
- };
- }
-
- private static void ReleaseMemoryChunks(MemoryChunk? chunk)
- {
- while (chunk != null)
- {
- chunk.Dispose();
- chunk = chunk.Next;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int GetChunkSize(int i)
- {
- // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator.
- // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720
-#pragma warning disable IDE1006 // Naming Styles
- const int _128K = 1 << 17;
- const int _4M = 1 << 22;
- return i < 16 ? _128K * (1 << (int)((uint)i / 4)) : _4M;
-#pragma warning restore IDE1006 // Naming Styles
- }
-
- private sealed class MemoryChunk : IDisposable
- {
- private bool isDisposed;
-
- public MemoryChunk(IMemoryOwner buffer) => this.Buffer = buffer;
-
- public IMemoryOwner Buffer { get; }
-
- public MemoryChunk? Next { get; set; }
-
- public int Length { get; init; }
-
- private void Dispose(bool disposing)
- {
- if (!this.isDisposed)
- {
- if (disposing)
- {
- this.Buffer.Dispose();
- }
-
- this.isDisposed = true;
- }
- }
-
- public void Dispose()
- {
- this.Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
- }
-}
diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs
index 63f9e64f6c..c73d2880a2 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp;
@@ -301,7 +300,7 @@ public abstract partial class Image
return action(stream);
}
- using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
+ using MemoryStream memoryStream = new();
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0;
@@ -343,7 +342,7 @@ public abstract partial class Image
return await action(stream, cancellationToken).ConfigureAwait(false);
}
- using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
+ await using MemoryStream memoryStream = new();
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
index d1d83ffb9a..dd94606084 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
@@ -529,6 +529,25 @@ public class WebpEncoderTests
[Fact]
public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic);
+ [Theory]
+ [WithFile(TestPatternOpaque, PixelTypes.Rgba32)]
+ public void CanSave_NonSeekableStream(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage();
+ WebpEncoder encoder = new();
+
+ using MemoryStream seekable = new();
+ image.Save(seekable, encoder);
+
+ using MemoryStream memoryStream = new();
+ using NonSeekableStream nonSeekable = new(memoryStream);
+
+ image.Save(nonSeekable, encoder);
+
+ Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray()));
+ }
+
private static ImageComparer GetComparer(int quality)
{
float tolerance = 0.01f; // ~1.0%
diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
deleted file mode 100644
index 1803cfddb9..0000000000
--- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs
+++ /dev/null
@@ -1,373 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.IO;
-using SixLabors.ImageSharp.Memory;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
-
-namespace SixLabors.ImageSharp.Tests.IO;
-
-///
-/// Tests for the class.
-///
-public class ChunkedMemoryStreamTests
-{
- ///
- /// The default length in bytes of each buffer chunk when allocating large buffers.
- ///
- private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
-
- ///
- /// The default length in bytes of each buffer chunk when allocating small buffers.
- ///
- private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
-
- private readonly MemoryAllocator allocator;
-
- public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator;
-
- [Fact]
- public void MemoryStream_GetPositionTest_Negative()
- {
- using var ms = new ChunkedMemoryStream(this.allocator);
- long iCurrentPos = ms.Position;
- for (int i = -1; i > -6; i--)
- {
- Assert.Throws(() => ms.Position = i);
- Assert.Equal(ms.Position, iCurrentPos);
- }
- }
-
- [Fact]
- public void MemoryStream_ReadTest_Negative()
- {
- var ms2 = new ChunkedMemoryStream(this.allocator);
-
- Assert.Throws(() => ms2.Read(null, 0, 0));
- Assert.Throws(() => ms2.Read(new byte[] { 1 }, -1, 0));
- Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, -1));
- Assert.Throws(() => ms2.Read(new byte[] { 1 }, 2, 0));
- Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 2));
-
- ms2.Dispose();
-
- Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 1));
- }
-
- [Theory]
- [InlineData(DefaultSmallChunkSize)]
- [InlineData((int)(DefaultSmallChunkSize * 1.5))]
- [InlineData(DefaultSmallChunkSize * 4)]
- [InlineData((int)(DefaultSmallChunkSize * 5.5))]
- [InlineData(DefaultSmallChunkSize * 16)]
- public void MemoryStream_ReadByteTest(int length)
- {
- using MemoryStream ms = this.CreateTestStream(length);
- using var cms = new ChunkedMemoryStream(this.allocator);
-
- ms.CopyTo(cms);
- cms.Position = 0;
- byte[] expected = ms.ToArray();
-
- for (int i = 0; i < expected.Length; i++)
- {
- Assert.Equal(expected[i], cms.ReadByte());
- }
- }
-
- [Theory]
- [InlineData(DefaultSmallChunkSize)]
- [InlineData((int)(DefaultSmallChunkSize * 1.5))]
- [InlineData(DefaultSmallChunkSize * 4)]
- [InlineData((int)(DefaultSmallChunkSize * 5.5))]
- [InlineData(DefaultSmallChunkSize * 16)]
- public void MemoryStream_ReadByteBufferTest(int length)
- {
- using MemoryStream ms = this.CreateTestStream(length);
- using var cms = new ChunkedMemoryStream(this.allocator);
-
- ms.CopyTo(cms);
- cms.Position = 0;
- byte[] expected = ms.ToArray();
- byte[] buffer = new byte[2];
- for (int i = 0; i < expected.Length; i += 2)
- {
- cms.Read(buffer);
- Assert.Equal(expected[i], buffer[0]);
- Assert.Equal(expected[i + 1], buffer[1]);
- }
- }
-
- [Theory]
- [InlineData(DefaultSmallChunkSize)]
- [InlineData((int)(DefaultSmallChunkSize * 1.5))]
- [InlineData(DefaultSmallChunkSize * 4)]
- [InlineData((int)(DefaultSmallChunkSize * 5.5))]
- [InlineData(DefaultSmallChunkSize * 16)]
- public void MemoryStream_ReadByteBufferSpanTest(int length)
- {
- using MemoryStream ms = this.CreateTestStream(length);
- using var cms = new ChunkedMemoryStream(this.allocator);
-
- ms.CopyTo(cms);
- cms.Position = 0;
- byte[] expected = ms.ToArray();
- Span buffer = new byte[2];
- for (int i = 0; i < expected.Length; i += 2)
- {
- cms.Read(buffer);
- Assert.Equal(expected[i], buffer[0]);
- Assert.Equal(expected[i + 1], buffer[1]);
- }
- }
-
- [Fact]
- public void MemoryStream_WriteToTests()
- {
- using (var ms2 = new ChunkedMemoryStream(this.allocator))
- {
- byte[] bytArrRet;
- byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
-
- // [] Write to memoryStream, check the memoryStream
- ms2.Write(bytArr, 0, bytArr.Length);
-
- using var readonlyStream = new ChunkedMemoryStream(this.allocator);
- ms2.WriteTo(readonlyStream);
- readonlyStream.Flush();
- readonlyStream.Position = 0;
- bytArrRet = new byte[(int)readonlyStream.Length];
- readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
- for (int i = 0; i < bytArr.Length; i++)
- {
- Assert.Equal(bytArr[i], bytArrRet[i]);
- }
- }
-
- // [] Write to memoryStream, check the memoryStream
- using (var ms2 = new ChunkedMemoryStream(this.allocator))
- using (var ms3 = new ChunkedMemoryStream(this.allocator))
- {
- byte[] bytArrRet;
- byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
-
- ms2.Write(bytArr, 0, bytArr.Length);
- ms2.WriteTo(ms3);
- ms3.Position = 0;
- bytArrRet = new byte[(int)ms3.Length];
- ms3.Read(bytArrRet, 0, (int)ms3.Length);
- for (int i = 0; i < bytArr.Length; i++)
- {
- Assert.Equal(bytArr[i], bytArrRet[i]);
- }
- }
- }
-
- [Fact]
- public void MemoryStream_WriteToSpanTests()
- {
- using (var ms2 = new ChunkedMemoryStream(this.allocator))
- {
- Span bytArrRet;
- Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
-
- // [] Write to memoryStream, check the memoryStream
- ms2.Write(bytArr, 0, bytArr.Length);
-
- using var readonlyStream = new ChunkedMemoryStream(this.allocator);
- ms2.WriteTo(readonlyStream);
- readonlyStream.Flush();
- readonlyStream.Position = 0;
- bytArrRet = new byte[(int)readonlyStream.Length];
- readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
- for (int i = 0; i < bytArr.Length; i++)
- {
- Assert.Equal(bytArr[i], bytArrRet[i]);
- }
- }
-
- // [] Write to memoryStream, check the memoryStream
- using (var ms2 = new ChunkedMemoryStream(this.allocator))
- using (var ms3 = new ChunkedMemoryStream(this.allocator))
- {
- Span bytArrRet;
- Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
-
- ms2.Write(bytArr, 0, bytArr.Length);
- ms2.WriteTo(ms3);
- ms3.Position = 0;
- bytArrRet = new byte[(int)ms3.Length];
- ms3.Read(bytArrRet, 0, (int)ms3.Length);
- for (int i = 0; i < bytArr.Length; i++)
- {
- Assert.Equal(bytArr[i], bytArrRet[i]);
- }
- }
- }
-
- [Fact]
- public void MemoryStream_WriteByteTests()
- {
- using (var ms2 = new ChunkedMemoryStream(this.allocator))
- {
- byte[] bytArrRet;
- byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 };
-
- for (int i = 0; i < bytArr.Length; i++)
- {
- ms2.WriteByte(bytArr[i]);
- }
-
- using var readonlyStream = new ChunkedMemoryStream(this.allocator);
- ms2.WriteTo(readonlyStream);
- readonlyStream.Flush();
- readonlyStream.Position = 0;
- bytArrRet = new byte[(int)readonlyStream.Length];
- readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length);
- for (int i = 0; i < bytArr.Length; i++)
- {
- Assert.Equal(bytArr[i], bytArrRet[i]);
- }
- }
- }
-
- [Fact]
- public void MemoryStream_WriteToTests_Negative()
- {
- using var ms2 = new ChunkedMemoryStream(this.allocator);
- Assert.Throws(() => ms2.WriteTo(null));
-
- ms2.Write(new byte[] { 1 }, 0, 1);
- var readonlyStream = new MemoryStream(new byte[1028], false);
- Assert.Throws(() => ms2.WriteTo(readonlyStream));
-
- readonlyStream.Dispose();
-
- // [] Pass in a closed stream
- Assert.Throws(() => ms2.WriteTo(readonlyStream));
- }
-
- [Fact]
- public void MemoryStream_CopyTo_Invalid()
- {
- ChunkedMemoryStream memoryStream;
- const string bufferSize = nameof(bufferSize);
- using (memoryStream = new ChunkedMemoryStream(this.allocator))
- {
- const string destination = nameof(destination);
- Assert.Throws(destination, () => memoryStream.CopyTo(destination: null));
-
- // Validate the destination parameter first.
- Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0));
- Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1));
-
- // Then bufferSize.
- Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense.
- Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
- }
-
- // After the Stream is disposed, we should fail on all CopyTos.
- Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated.
- Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1));
-
- ChunkedMemoryStream disposedStream = memoryStream;
-
- // We should throw first for the source being disposed...
- Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1));
-
- // Then for the destination being disposed.
- memoryStream = new ChunkedMemoryStream(this.allocator);
- Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1));
- memoryStream.Dispose();
- }
-
- [Theory]
- [MemberData(nameof(CopyToData))]
- public void CopyTo(Stream source, byte[] expected)
- {
- using var destination = new ChunkedMemoryStream(this.allocator);
- source.CopyTo(destination);
- Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end.
- Assert.Equal(expected, destination.ToArray());
- }
-
- public static IEnumerable GetAllTestImages()
- {
- IEnumerable allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories)
- .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase));
-
- var result = new List();
- foreach (string path in allImageFiles)
- {
- result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length));
- }
-
- return result;
- }
-
- public static IEnumerable AllTestImages = GetAllTestImages();
-
- [Theory]
- [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)]
- public void DecoderIntegrationTest(TestImageProvider provider)
- where TPixel : unmanaged, IPixel
- {
- if (!TestEnvironment.Is64BitProcess)
- {
- return;
- }
-
- Image expected;
- try
- {
- expected = provider.GetImage();
- }
- catch
- {
- // The image is invalid
- return;
- }
-
- string fullPath = Path.Combine(
- TestEnvironment.InputImagesDirectoryFullPath,
- ((TestImageProvider.FileProvider)provider).FilePath);
-
- using FileStream fs = File.OpenRead(fullPath);
- using var nonSeekableStream = new NonSeekableStream(fs);
-
- var actual = Image.Load(nonSeekableStream);
-
- ImageComparer.Exact.VerifySimilarity(expected, actual);
- }
-
- public static IEnumerable