Skip to content

Commit ef5e21b

Browse files
Merge pull request #1269 from SixLabors/js/buffered-read
Buffered Decoding.
2 parents 7ee0207 + e6c78c2 commit ef5e21b

34 files changed

+1515
-909
lines changed

src/ImageSharp/Common/Extensions/StreamExtensions.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers;
56
using System.IO;
67
using SixLabors.ImageSharp.Memory;
7-
#if !SUPPORTS_SPAN_STREAM
8-
using System.Buffers;
9-
#endif
108

119
namespace SixLabors.ImageSharp
1210
{
@@ -40,7 +38,7 @@ public static int Read(this Stream stream, Span<byte> buffer, int offset, int co
4038
/// Skips the number of bytes in the given stream.
4139
/// </summary>
4240
/// <param name="stream">The stream.</param>
43-
/// <param name="count">The count.</param>
41+
/// <param name="count">A byte offset relative to the origin parameter.</param>
4442
public static void Skip(this Stream stream, int count)
4543
{
4644
if (count < 1)
@@ -50,14 +48,16 @@ public static void Skip(this Stream stream, int count)
5048

5149
if (stream.CanSeek)
5250
{
53-
stream.Seek(count, SeekOrigin.Current); // Position += count;
51+
stream.Seek(count, SeekOrigin.Current);
52+
return;
5453
}
55-
else
54+
55+
byte[] buffer = ArrayPool<byte>.Shared.Rent(count);
56+
try
5657
{
57-
var foo = new byte[count];
5858
while (count > 0)
5959
{
60-
int bytesRead = stream.Read(foo, 0, count);
60+
int bytesRead = stream.Read(buffer, 0, count);
6161
if (bytesRead == 0)
6262
{
6363
break;
@@ -66,6 +66,10 @@ public static void Skip(this Stream stream, int count)
6666
count -= bytesRead;
6767
}
6868
}
69+
finally
70+
{
71+
ArrayPool<byte>.Shared.Return(buffer);
72+
}
6973
}
7074

7175
public static void Read(this Stream stream, IManagedByteBuffer buffer)

src/ImageSharp/Formats/Bmp/BmpDecoder.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.IO;
55
using System.Threading.Tasks;
6+
using SixLabors.ImageSharp.IO;
67
using SixLabors.ImageSharp.Memory;
78
using SixLabors.ImageSharp.PixelFormats;
89

@@ -29,65 +30,71 @@ public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDe
2930
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
3031

3132
/// <inheritdoc/>
32-
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
33-
where TPixel : unmanaged, IPixel<TPixel>
33+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
34+
where TPixel : unmanaged, IPixel<TPixel>
3435
{
3536
Guard.NotNull(stream, nameof(stream));
3637

3738
var decoder = new BmpDecoderCore(configuration, this);
3839

3940
try
4041
{
41-
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
42+
using var bufferedStream = new BufferedReadStream(stream);
43+
return decoder.Decode<TPixel>(bufferedStream);
4244
}
4345
catch (InvalidMemoryOperationException ex)
4446
{
4547
Size dims = decoder.Dimensions;
4648

47-
throw new InvalidImageContentException($"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);
49+
throw new InvalidImageContentException($"Cannot 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);
4850
}
4951
}
5052

53+
/// <inheritdoc />
54+
public Image Decode(Configuration configuration, Stream stream)
55+
=> this.Decode<Rgba32>(configuration, stream);
56+
5157
/// <inheritdoc/>
52-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
53-
where TPixel : unmanaged, IPixel<TPixel>
58+
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
59+
where TPixel : unmanaged, IPixel<TPixel>
5460
{
5561
Guard.NotNull(stream, nameof(stream));
5662

5763
var decoder = new BmpDecoderCore(configuration, this);
5864

5965
try
6066
{
61-
return decoder.Decode<TPixel>(stream);
67+
using var bufferedStream = new BufferedReadStream(stream);
68+
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
6269
}
6370
catch (InvalidMemoryOperationException ex)
6471
{
6572
Size dims = decoder.Dimensions;
6673

67-
throw new InvalidImageContentException($"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);
74+
throw new InvalidImageContentException($"Cannot 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);
6875
}
6976
}
7077

7178
/// <inheritdoc />
72-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
73-
74-
/// <inheritdoc />
75-
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
79+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
80+
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
7681

7782
/// <inheritdoc/>
7883
public IImageInfo Identify(Configuration configuration, Stream stream)
7984
{
8085
Guard.NotNull(stream, nameof(stream));
8186

82-
return new BmpDecoderCore(configuration, this).Identify(stream);
87+
using var bufferedStream = new BufferedReadStream(stream);
88+
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
8389
}
8490

8591
/// <inheritdoc/>
8692
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
8793
{
8894
Guard.NotNull(stream, nameof(stream));
8995

90-
return new BmpDecoderCore(configuration, this).IdentifyAsync(stream);
96+
using var bufferedStream = new BufferedReadStream(stream);
97+
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
9198
}
9299
}
93100
}

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
using System;
55
using System.Buffers;
66
using System.Buffers.Binary;
7-
using System.IO;
87
using System.Numerics;
98
using System.Runtime.CompilerServices;
10-
using System.Threading.Tasks;
119
using SixLabors.ImageSharp.Common.Helpers;
1210
using SixLabors.ImageSharp.IO;
1311
using SixLabors.ImageSharp.Memory;
@@ -62,7 +60,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
6260
/// <summary>
6361
/// The stream to decode from.
6462
/// </summary>
65-
private Stream stream;
63+
private BufferedReadStream stream;
6664

6765
/// <summary>
6866
/// The metadata.
@@ -120,7 +118,7 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
120118
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
121119

122120
/// <inheritdoc />
123-
public Image<TPixel> Decode<TPixel>(Stream stream)
121+
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
124122
where TPixel : unmanaged, IPixel<TPixel>
125123
{
126124
try
@@ -199,7 +197,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
199197
}
200198

201199
/// <inheritdoc />
202-
public IImageInfo Identify(Stream stream)
200+
public IImageInfo Identify(BufferedReadStream stream)
203201
{
204202
this.ReadImageHeaders(stream, out _, out _);
205203
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
@@ -1355,7 +1353,7 @@ private void ReadFileHeader()
13551353
/// </summary>
13561354
/// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
13571355
/// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
1358-
private int ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
1356+
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
13591357
{
13601358
this.stream = stream;
13611359

src/ImageSharp/Formats/Gif/GifDecoder.cs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4-
using System;
54
using System.IO;
65
using System.Threading.Tasks;
6+
using SixLabors.ImageSharp.IO;
77
using SixLabors.ImageSharp.Memory;
88
using SixLabors.ImageSharp.Metadata;
99
using SixLabors.ImageSharp.PixelFormats;
@@ -26,54 +26,66 @@ public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDe
2626
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
2727

2828
/// <inheritdoc/>
29-
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
29+
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
3030
where TPixel : unmanaged, IPixel<TPixel>
3131
{
3232
var decoder = new GifDecoderCore(configuration, this);
3333

3434
try
3535
{
36-
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
36+
using var bufferedStream = new BufferedReadStream(stream);
37+
return decoder.Decode<TPixel>(bufferedStream);
3738
}
3839
catch (InvalidMemoryOperationException ex)
3940
{
4041
Size dims = decoder.Dimensions;
4142

42-
GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
43+
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
4344

4445
// Not reachable, as the previous statement will throw a exception.
4546
return null;
4647
}
4748
}
4849

50+
/// <inheritdoc />
51+
public Image Decode(Configuration configuration, Stream stream)
52+
=> this.Decode<Rgba32>(configuration, stream);
53+
4954
/// <inheritdoc/>
50-
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
55+
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
5156
where TPixel : unmanaged, IPixel<TPixel>
5257
{
5358
var decoder = new GifDecoderCore(configuration, this);
5459

5560
try
5661
{
57-
return decoder.Decode<TPixel>(stream);
62+
using var bufferedStream = new BufferedReadStream(stream);
63+
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
5864
}
5965
catch (InvalidMemoryOperationException ex)
6066
{
6167
Size dims = decoder.Dimensions;
6268

63-
GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
69+
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
6470

6571
// Not reachable, as the previous statement will throw a exception.
6672
return null;
6773
}
6874
}
6975

76+
/// <inheritdoc />
77+
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
78+
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
79+
7080
/// <inheritdoc/>
7181
public IImageInfo Identify(Configuration configuration, Stream stream)
7282
{
7383
Guard.NotNull(stream, nameof(stream));
7484

7585
var decoder = new GifDecoderCore(configuration, this);
76-
return decoder.Identify(stream);
86+
87+
using var bufferedStream = new BufferedReadStream(stream);
88+
return decoder.Identify(bufferedStream);
7789
}
7890

7991
/// <inheritdoc/>
@@ -82,13 +94,9 @@ public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream
8294
Guard.NotNull(stream, nameof(stream));
8395

8496
var decoder = new GifDecoderCore(configuration, this);
85-
return decoder.IdentifyAsync(stream);
86-
}
8797

88-
/// <inheritdoc />
89-
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
90-
91-
/// <inheritdoc />
92-
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
98+
using var bufferedStream = new BufferedReadStream(stream);
99+
return decoder.IdentifyAsync(bufferedStream);
100+
}
93101
}
94102
}

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
2727
/// <summary>
2828
/// The currently loaded stream.
2929
/// </summary>
30-
private Stream stream;
30+
private BufferedReadStream stream;
3131

3232
/// <summary>
3333
/// The global color table.
@@ -97,7 +97,7 @@ public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
9797
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
9898

9999
/// <inheritdoc />
100-
public Image<TPixel> Decode<TPixel>(Stream stream)
100+
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
101101
where TPixel : unmanaged, IPixel<TPixel>
102102
{
103103
Image<TPixel> image = null;
@@ -158,7 +158,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
158158
}
159159

160160
/// <inheritdoc />
161-
public IImageInfo Identify(Stream stream)
161+
public IImageInfo Identify(BufferedReadStream stream)
162162
{
163163
try
164164
{
@@ -572,7 +572,7 @@ private void SetFrameMetadata(ImageFrameMetadata meta)
572572
/// Reads the logical screen descriptor and global color table blocks
573573
/// </summary>
574574
/// <param name="stream">The stream containing image data. </param>
575-
private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream)
575+
private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream)
576576
{
577577
this.stream = stream;
578578

src/ImageSharp/Formats/Gif/LzwDecoder.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33

44
using System;
55
using System.Buffers;
6-
using System.IO;
76
using System.Runtime.CompilerServices;
87
using System.Runtime.InteropServices;
9-
8+
using SixLabors.ImageSharp.IO;
109
using SixLabors.ImageSharp.Memory;
1110

1211
namespace SixLabors.ImageSharp.Formats.Gif
@@ -29,7 +28,7 @@ internal sealed class LzwDecoder : IDisposable
2928
/// <summary>
3029
/// The stream to decode.
3130
/// </summary>
32-
private readonly Stream stream;
31+
private readonly BufferedReadStream stream;
3332

3433
/// <summary>
3534
/// The prefix buffer.
@@ -52,8 +51,8 @@ internal sealed class LzwDecoder : IDisposable
5251
/// </summary>
5352
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
5453
/// <param name="stream">The stream to read from.</param>
55-
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
56-
public LzwDecoder(MemoryAllocator memoryAllocator, Stream stream)
54+
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
55+
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
5756
{
5857
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
5958

src/ImageSharp/Formats/IImageDecoderInternals.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4-
using System.IO;
4+
using System;
5+
using SixLabors.ImageSharp.IO;
56
using SixLabors.ImageSharp.PixelFormats;
67

78
namespace SixLabors.ImageSharp.Formats
@@ -21,18 +22,16 @@ internal interface IImageDecoderInternals
2122
/// </summary>
2223
/// <typeparam name="TPixel">The pixel format.</typeparam>
2324
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
24-
/// <exception cref="System.ArgumentNullException">
25-
/// <para><paramref name="stream"/> is null.</para>
26-
/// </exception>
25+
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
2726
/// <returns>The decoded image.</returns>
28-
Image<TPixel> Decode<TPixel>(Stream stream)
27+
Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
2928
where TPixel : unmanaged, IPixel<TPixel>;
3029

3130
/// <summary>
3231
/// Reads the raw image information from the specified stream.
3332
/// </summary>
34-
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
33+
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
3534
/// <returns>The <see cref="IImageInfo"/>.</returns>
36-
IImageInfo Identify(Stream stream);
35+
IImageInfo Identify(BufferedReadStream stream);
3736
}
3837
}

0 commit comments

Comments
 (0)