diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index e5fe6f807..0f6e9da1e 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -66,20 +66,16 @@ namespace SixLabors.ImageSharp.IO this.readBufferIndex = BufferLength; } - /// - /// Gets the length, in bytes, of the stream. - /// + /// public override long Length { get; } - /// - /// Gets or sets the current position within the stream. - /// + /// public override long Position { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.readerPosition; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] set { // Only reset readBufferIndex if we are out of bounds of our working buffer @@ -185,12 +181,16 @@ namespace SixLabors.ImageSharp.IO } /// - /// This operation is not supported in . + /// + /// This operation is not supported in . + /// public override void SetLength(long value) => throw new NotSupportedException(); /// - /// This operation is not supported in . + /// + /// This operation is not supported in . + /// public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.IO return n; } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) { // Refill our buffer then copy. diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs deleted file mode 100644 index 0345717d2..000000000 --- a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.IO; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.IO -{ - /// - /// A stream reader that add a secondary level buffer in addition to native stream buffered reading - /// to reduce the overhead of small incremental reads. - /// - internal sealed unsafe class DoubleBufferedStreamReader : IDisposable - { - /// - /// The length, in bytes, of the buffering chunk. - /// - public const int ChunkLength = 8192; - - private const int MaxChunkIndex = ChunkLength - 1; - - private readonly Stream stream; - - private readonly IManagedByteBuffer managedBuffer; - - private MemoryHandle handle; - - private readonly byte* pinnedChunk; - - private readonly byte[] bufferChunk; - - private readonly int length; - - private int chunkIndex; - - private int position; - - /// - /// Initializes a new instance of the class. - /// - /// The to use for buffer allocations. - /// The input stream. - public DoubleBufferedStreamReader(MemoryAllocator memoryAllocator, Stream stream) - { - this.stream = stream; - this.Position = (int)stream.Position; - this.length = (int)stream.Length; - this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength); - this.bufferChunk = this.managedBuffer.Array; - this.handle = this.managedBuffer.Memory.Pin(); - this.pinnedChunk = (byte*)this.handle.Pointer; - this.chunkIndex = ChunkLength; - } - - /// - /// Gets the length, in bytes, of the stream. - /// - public long Length => this.length; - - /// - /// Gets or sets the current position within the stream. - /// - public long Position - { - get => this.position; - - set - { - // Only reset chunkIndex if we are out of bounds of our working chunk - // otherwise we should simply move the value by the diff. - int v = (int)value; - if (this.IsInChunk(v, out int index)) - { - this.chunkIndex = index; - this.position = v; - } - else - { - this.position = v; - this.stream.Seek(value, SeekOrigin.Begin); - this.chunkIndex = ChunkLength; - } - } - } - - /// - /// Reads a byte from the stream and advances the position within the stream by one - /// byte, or returns -1 if at the end of the stream. - /// - /// The unsigned byte cast to an , or -1 if at the end of the stream. - [MethodImpl(InliningOptions.ShortMethod)] - public int ReadByte() - { - if (this.position >= this.length) - { - return -1; - } - - if (this.chunkIndex > MaxChunkIndex) - { - this.FillChunk(); - } - - this.position++; - return this.pinnedChunk[this.chunkIndex++]; - } - - /// - /// Skips the number of bytes in the stream - /// - /// The number of bytes to skip. - [MethodImpl(InliningOptions.ShortMethod)] - public void Skip(int count) => this.Position += count; - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream - /// by the number of bytes read. - /// - /// - /// An array of bytes. When this method returns, the buffer contains the specified - /// byte array with the values between offset and (offset + count - 1) replaced by - /// the bytes read from the current source. - /// - /// - /// The zero-based byte offset in buffer at which to begin storing the data read - /// from the current stream. - /// - /// The maximum number of bytes to be read from the current stream. - /// - /// The total number of bytes read into the buffer. This can be less than the number - /// of bytes requested if that many bytes are not currently available, or zero (0) - /// if the end of the stream has been reached. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int Read(byte[] buffer, int offset, int count) - { - if (count > ChunkLength) - { - return this.ReadToBufferSlow(buffer, offset, count); - } - - if (count + this.chunkIndex > ChunkLength) - { - return this.ReadToChunkSlow(buffer, offset, count); - } - - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.chunkIndex += n; - return n; - } - - /// - public void Dispose() - { - this.handle.Dispose(); - this.managedBuffer?.Dispose(); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int GetPositionDifference(int p) => p - this.position; - - [MethodImpl(InliningOptions.ShortMethod)] - private bool IsInChunk(int p, out int index) - { - index = this.GetPositionDifference(p) + this.chunkIndex; - return index > -1 && index < ChunkLength; - } - - [MethodImpl(InliningOptions.ColdPath)] - private void FillChunk() - { - if (this.position != this.stream.Position) - { - this.stream.Seek(this.position, SeekOrigin.Begin); - } - - this.stream.Read(this.bufferChunk, 0, ChunkLength); - this.chunkIndex = 0; - } - - [MethodImpl(InliningOptions.ColdPath)] - private int ReadToChunkSlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - this.FillChunk(); - - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.chunkIndex += n; - - return n; - } - - [MethodImpl(InliningOptions.ColdPath)] - private int ReadToBufferSlow(byte[] buffer, int offset, int count) - { - // Read to target but don't copy to our chunk. - if (this.position != this.stream.Position) - { - this.stream.Seek(this.position, SeekOrigin.Begin); - } - - int n = this.stream.Read(buffer, offset, count); - this.Position += n; - - return n; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyCount(int count) - { - int n = this.length - this.position; - if (n > count) - { - n = count; - } - - if (n < 0) - { - n = 0; - } - - return n; - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void CopyBytes(byte[] buffer, int offset, int count) - { - if (count < 9) - { - int byteCount = count; - int read = this.chunkIndex; - byte* pinned = this.pinnedChunk; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = pinned[read + byteCount]; - } - } - else - { - Buffer.BlockCopy(this.bufferChunk, this.chunkIndex, buffer, offset, count); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 1696623ef..8345d863e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public ShortClr() { // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), - this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); + this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); } } } @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - #pragma warning disable SA1115 +#pragma warning disable SA1115 [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, diff --git a/src/ImageSharp/IO/BufferedReadStream2.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs similarity index 67% rename from src/ImageSharp/IO/BufferedReadStream2.cs rename to tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs index a35804ce2..76a48af21 100644 --- a/src/ImageSharp/IO/BufferedReadStream2.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs @@ -6,13 +6,13 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.IO +namespace SixLabors.ImageSharp.Benchmarks.IO { /// - /// A readonly stream that add a secondary level buffer in addition to native stream + /// A readonly stream wrapper that add a secondary level buffer in addition to native stream /// buffered reading to reduce the overhead of small incremental reads. /// - internal sealed unsafe class BufferedReadStream2 : IDisposable + internal sealed unsafe class BufferedReadStreamWrapper : IDisposable { /// /// The length, in bytes, of the underlying buffer. @@ -29,19 +29,19 @@ namespace SixLabors.ImageSharp.IO private readonly byte* pinnedReadBuffer; + // Index within our buffer, not reader position. private int readBufferIndex; - private readonly int length; - - private int position; + // Matches what the stream position would be without buffering + private long readerPosition; private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The input stream. - public BufferedReadStream2(Stream stream) + public BufferedReadStreamWrapper(Stream stream) { Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.IO this.stream = stream; this.Position = (int)stream.Position; - this.length = (int)stream.Length; + this.Length = stream.Length; this.readBuffer = ArrayPool.Shared.Rent(BufferLength); this.readBufferHandle = new Memory(this.readBuffer).Pin(); @@ -69,113 +69,109 @@ namespace SixLabors.ImageSharp.IO /// /// Gets the length, in bytes, of the stream. /// - public long Length => this.length; + public long Length { get; } /// /// Gets or sets the current position within the stream. /// public long Position { - get => this.position; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.readerPosition; + [MethodImpl(MethodImplOptions.NoInlining)] set { - // Only reset readIndex if we are out of bounds of our working buffer + // Only reset readBufferIndex if we are out of bounds of our working buffer // otherwise we should simply move the value by the diff. - int v = (int)value; - if (this.IsInReadBuffer(v, out int index)) + if (this.IsInReadBuffer(value, out long index)) { - this.readBufferIndex = index; - this.position = v; + this.readBufferIndex = (int)index; + this.readerPosition = value; } else { - this.position = v; + // Base stream seek will throw for us if invalid. this.stream.Seek(value, SeekOrigin.Begin); + this.readerPosition = value; this.readBufferIndex = BufferLength; } } } - public bool CanRead { get; } = true; - - public bool CanSeek { get; } = true; - - public bool CanWrite { get; } = false; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadByte() { - if (this.position >= this.length) + if (this.readerPosition >= this.Length) { return -1; } + // Our buffer has been read. + // We need to refill and start again. if (this.readBufferIndex > MaxBufferIndex) { this.FillReadBuffer(); } - this.position++; + this.readerPosition++; return this.pinnedReadBuffer[this.readBufferIndex++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Read(byte[] buffer, int offset, int count) { + // Too big for our buffer. Read directly from the stream. if (count > BufferLength) { return this.ReadToBufferDirectSlow(buffer, offset, count); } + // Too big for remaining buffer but less than entire buffer length + // Copy to buffer then read from there. if (count + this.readBufferIndex > BufferLength) { return this.ReadToBufferViaCopySlow(buffer, offset, count); } - // return this.ReadToBufferViaCopyFast(buffer, offset, count); - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.readBufferIndex += n; - - return n; + return this.ReadToBufferViaCopyFast(buffer, offset, count); } public void Flush() { - // Reset the stream position. - if (this.position != this.stream.Position) + // Reset the stream position to match reader position. + if (this.readerPosition != this.stream.Position) { - this.stream.Seek(this.position, SeekOrigin.Begin); - this.position = (int)this.stream.Position; + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); + this.readerPosition = (int)this.stream.Position; } + // Reset to trigger full read on next attempt. this.readBufferIndex = BufferLength; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public long Seek(long offset, SeekOrigin origin) { - if (origin == SeekOrigin.Begin) + switch (origin) { - this.Position = offset; - } - else - { - this.Position += offset; - } + case SeekOrigin.Begin: + this.Position = offset; + break; - return this.position; - } + case SeekOrigin.Current: + this.Position += offset; + break; - public void SetLength(long value) - => throw new NotSupportedException(); + case SeekOrigin.End: + this.Position = this.Length - offset; + break; + } - public void Write(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); + return this.readerPosition; + } + /// public void Dispose() { if (!this.isDisposed) @@ -188,21 +184,18 @@ namespace SixLabors.ImageSharp.IO } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetPositionDifference(int p) => p - this.position; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsInReadBuffer(int p, out int index) + private bool IsInReadBuffer(long newPosition, out long index) { - index = this.GetPositionDifference(p) + this.readBufferIndex; + index = newPosition - this.readerPosition + this.readBufferIndex; return index > -1 && index < BufferLength; } [MethodImpl(MethodImplOptions.NoInlining)] private void FillReadBuffer() { - if (this.position != this.stream.Position) + if (this.readerPosition != this.stream.Position) { - this.stream.Seek(this.position, SeekOrigin.Begin); + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); } this.stream.Read(this.readBuffer, 0, BufferLength); @@ -215,35 +208,28 @@ namespace SixLabors.ImageSharp.IO int n = this.GetCopyCount(count); this.CopyBytes(buffer, offset, n); - this.position += n; + this.readerPosition += n; this.readBufferIndex += n; return n; } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) { // Refill our buffer then copy. this.FillReadBuffer(); - // return this.ReadToBufferViaCopyFast(buffer, offset, count); - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); - - this.position += n; - this.readBufferIndex += n; - - return n; + return this.ReadToBufferViaCopyFast(buffer, offset, count); } [MethodImpl(MethodImplOptions.NoInlining)] private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) { // Read to target but don't copy to our read buffer. - if (this.position != this.stream.Position) + if (this.readerPosition != this.stream.Position) { - this.stream.Seek(this.position, SeekOrigin.Begin); + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); } int n = this.stream.Read(buffer, offset, count); @@ -255,18 +241,18 @@ namespace SixLabors.ImageSharp.IO [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetCopyCount(int count) { - int n = this.length - this.position; + long n = this.Length - this.readerPosition; if (n > count) { - n = count; + return count; } if (n < 0) { - n = 0; + return 0; } - return n; + return (int)n; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs similarity index 73% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs rename to tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs index 389a74326..c5064aeea 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs @@ -6,10 +6,10 @@ using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.IO { [Config(typeof(Config.ShortClr))] - public class DoubleBufferedStreams + public class BufferedStreams { private readonly byte[] buffer = CreateTestBytes(); private readonly byte[] chunk1 = new byte[2]; @@ -21,14 +21,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private MemoryStream stream4; private MemoryStream stream5; private MemoryStream stream6; - private MemoryStream stream7; - private MemoryStream stream8; - private DoubleBufferedStreamReader reader1; - private DoubleBufferedStreamReader reader2; private BufferedReadStream bufferedStream1; private BufferedReadStream bufferedStream2; - private BufferedReadStream2 bufferedStream3; - private BufferedReadStream2 bufferedStream4; + private BufferedReadStreamWrapper bufferedStreamWrap1; + private BufferedReadStreamWrapper bufferedStreamWrap2; [GlobalSetup] public void CreateStreams() @@ -39,31 +35,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg this.stream4 = new MemoryStream(this.buffer); this.stream5 = new MemoryStream(this.buffer); this.stream6 = new MemoryStream(this.buffer); - this.stream7 = new MemoryStream(this.buffer); - this.stream8 = new MemoryStream(this.buffer); - this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2); - this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2); - this.bufferedStream1 = new BufferedReadStream(this.stream5); - this.bufferedStream2 = new BufferedReadStream(this.stream6); - this.bufferedStream3 = new BufferedReadStream2(this.stream7); - this.bufferedStream4 = new BufferedReadStream2(this.stream8); + this.bufferedStream1 = new BufferedReadStream(this.stream3); + this.bufferedStream2 = new BufferedReadStream(this.stream4); + this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5); + this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6); } [GlobalCleanup] public void DestroyStreams() { + this.bufferedStream1?.Dispose(); + this.bufferedStream2?.Dispose(); + this.bufferedStreamWrap1?.Dispose(); + this.bufferedStreamWrap2?.Dispose(); this.stream1?.Dispose(); this.stream2?.Dispose(); this.stream3?.Dispose(); this.stream4?.Dispose(); this.stream5?.Dispose(); this.stream6?.Dispose(); - this.reader1?.Dispose(); - this.reader2?.Dispose(); - this.bufferedStream1?.Dispose(); - this.bufferedStream2?.Dispose(); - this.bufferedStream3?.Dispose(); - this.bufferedStream4?.Dispose(); } [Benchmark] @@ -82,10 +72,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public int DoubleBufferedStreamRead() + public int BufferedReadStreamRead() { int r = 0; - DoubleBufferedStreamReader reader = this.reader2; + BufferedReadStream reader = this.bufferedStream1; byte[] b = this.chunk2; for (int i = 0; i < reader.Length / 2; i++) @@ -97,25 +87,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public int BufferedStreamRead() + public int BufferedReadStreamWrapRead() { int r = 0; - BufferedReadStream reader = this.bufferedStream2; - byte[] b = this.chunk2; - - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } - - return r; - } - - [Benchmark] - public int BufferedStreamWrapRead() - { - int r = 0; - BufferedReadStream2 reader = this.bufferedStream3; + BufferedReadStreamWrapper reader = this.bufferedStreamWrap1; byte[] b = this.chunk2; for (int i = 0; i < reader.Length / 2; i++) @@ -130,7 +105,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public int StandardStreamReadByte() { int r = 0; - Stream stream = this.stream1; + Stream stream = this.stream2; for (int i = 0; i < stream.Length; i++) { @@ -141,21 +116,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public int DoubleBufferedStreamReadByte() - { - int r = 0; - DoubleBufferedStreamReader reader = this.reader1; - - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } - - return r; - } - - [Benchmark] - public int BufferedStreamReadByte() + public int BufferedReadStreamReadByte() { int r = 0; BufferedReadStream reader = this.bufferedStream2; @@ -169,10 +130,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public int BufferedStreamWrapReadByte() + public int BufferedReadStreamWrapReadByte() { int r = 0; - BufferedReadStream2 reader = this.bufferedStream4; + BufferedReadStreamWrapper reader = this.bufferedStreamWrap2; for (int i = 0; i < reader.Length; i++) { @@ -197,7 +158,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private static byte[] CreateTestBytes() { - var buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; + var buffer = new byte[BufferedReadStream.BufferLength * 3]; var random = new Random(); random.NextBytes(buffer); diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index f380d0a6a..e26fba627 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -31,7 +31,6 @@ - diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs index 992e2536d..748550a54 100644 --- a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs @@ -199,6 +199,22 @@ namespace SixLabors.ImageSharp.Tests.IO } } + [Fact] + public void BufferedStreamReadsCanReadAllAsSingleByteFromOrigin() + { + using (MemoryStream stream = this.CreateTestStream()) + { + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(stream)) + { + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], reader.ReadByte()); + } + } + } + } + private MemoryStream CreateTestStream(int length = BufferedReadStream.BufferLength * 3) { var buffer = new byte[length]; diff --git a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs deleted file mode 100644 index 62e204843..000000000 --- a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.IO -{ - public class DoubleBufferedStreamReaderTests - { - private readonly MemoryAllocator allocator = Configuration.Default.MemoryAllocator; - - [Fact] - public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin() - { - using (MemoryStream stream = this.CreateTestStream()) - { - byte[] expected = stream.ToArray(); - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - - Assert.Equal(expected[0], reader.ReadByte()); - - // We've read a whole chunk but increment by 1 in our reader. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); - Assert.Equal(1, reader.Position); - } - } - - [Fact] - public void DoubleBufferedStreamReaderCanReadSingleByteFromOffset() - { - using (MemoryStream stream = this.CreateTestStream()) - { - byte[] expected = stream.ToArray(); - const int offset = 5; - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - reader.Position = offset; - - Assert.Equal(expected[offset], reader.ReadByte()); - - // We've read a whole chunk but increment by 1 in our reader. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength + offset); - Assert.Equal(offset + 1, reader.Position); - } - } - - [Fact] - public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly() - { - using (MemoryStream stream = this.CreateTestStream()) - { - byte[] expected = stream.ToArray(); - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], reader.ReadByte()); - Assert.Equal(i + 1, reader.Position); - - if (i < DoubleBufferedStreamReader.ChunkLength) - { - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); - } - else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2) - { - // We should have advanced to the second chunk now. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); - } - else - { - // We should have advanced to the third chunk now. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); - } - } - } - } - - [Fact] - public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin() - { - using (MemoryStream stream = this.CreateTestStream()) - { - var buffer = new byte[2]; - byte[] expected = stream.ToArray(); - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - - Assert.Equal(2, reader.Read(buffer, 0, 2)); - Assert.Equal(expected[0], buffer[0]); - Assert.Equal(expected[1], buffer[1]); - - // We've read a whole chunk but increment by the buffer length in our reader. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); - Assert.Equal(buffer.Length, reader.Position); - } - } - - [Fact] - public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly() - { - using (MemoryStream stream = this.CreateTestStream()) - { - var buffer = new byte[2]; - byte[] expected = stream.ToArray(); - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - - for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) - { - Assert.Equal(2, reader.Read(buffer, 0, 2)); - Assert.Equal(expected[o], buffer[0]); - Assert.Equal(expected[o + 1], buffer[1]); - Assert.Equal(o + 2, reader.Position); - - int offset = i * 2; - if (offset < DoubleBufferedStreamReader.ChunkLength) - { - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength); - } - else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2) - { - // We should have advanced to the second chunk now. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2); - } - else - { - // We should have advanced to the third chunk now. - Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3); - } - } - } - } - - [Fact] - public void DoubleBufferedStreamReaderCanSkip() - { - using (MemoryStream stream = this.CreateTestStream()) - { - byte[] expected = stream.ToArray(); - var reader = new DoubleBufferedStreamReader(this.allocator, stream); - - int skip = 50; - int plusOne = 1; - int skip2 = DoubleBufferedStreamReader.ChunkLength; - - // Skip - reader.Skip(skip); - Assert.Equal(skip, reader.Position); - Assert.Equal(stream.Position, reader.Position); - - // Read - Assert.Equal(expected[skip], reader.ReadByte()); - - // Skip Again - reader.Skip(skip2); - - // First Skip + First Read + Second Skip - int position = skip + plusOne + skip2; - - Assert.Equal(position, reader.Position); - Assert.Equal(stream.Position, reader.Position); - Assert.Equal(expected[position], reader.ReadByte()); - } - } - - private MemoryStream CreateTestStream() - { - var buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3]; - var random = new Random(); - random.NextBytes(buffer); - - return new MemoryStream(buffer); - } - } -}