Browse Source

Use explicit stream instance in core decoders.

pull/1574/head
James Jackson-South 6 years ago
parent
commit
5537435bb0
  1. 35
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  2. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 38
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 8
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  5. 9
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  6. 15
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  7. 45
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  8. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  9. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  10. 45
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  11. 33
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  12. 53
      src/ImageSharp/Formats/Png/PngDecoder.cs
  13. 13
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  14. 9
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  15. 31
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  16. 11
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  17. 94
      src/ImageSharp/IO/BufferedReadStream.cs
  18. 66
      src/ImageSharp/Image.FromStream.cs
  19. 23
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  20. 20
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  21. 23
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  22. 15
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  23. 36
      tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs
  24. 27
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs
  25. 13
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs

35
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -29,8 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -38,19 +39,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
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);
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);
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -58,28 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp
try
{
return decoder.Decode<TPixel>(stream);
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
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);
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);
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).Identify(stream);
using var bufferedStream = new BufferedReadStream(stream);
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
}
/// <inheritdoc/>
@ -87,7 +93,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).IdentifyAsync(stream);
using var bufferedStream = new BufferedReadStream(stream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
}
}
}

10
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -4,10 +4,8 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -62,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream stream;
private BufferedReadStream stream;
/// <summary>
/// The metadata.
@ -120,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(Stream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
try
@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <inheritdoc />
public IImageInfo Identify(Stream stream)
public IImageInfo Identify(BufferedReadStream stream)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
@ -1355,7 +1353,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
/// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
/// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
private int ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
{
this.stream = stream;

38
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -26,54 +26,66 @@ namespace SixLabors.ImageSharp.Formats.Gif
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.Identify(stream);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
}
/// <inheritdoc/>
@ -82,13 +94,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.IdentifyAsync(stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
}
}
}

8
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The currently loaded stream.
/// </summary>
private Stream stream;
private BufferedReadStream stream;
/// <summary>
/// The global color table.
@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(Stream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
@ -158,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <inheritdoc />
public IImageInfo Identify(Stream stream)
public IImageInfo Identify(BufferedReadStream stream)
{
try
{
@ -572,7 +572,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Reads the logical screen descriptor and global color table blocks
/// </summary>
/// <param name="stream">The stream containing image data. </param>
private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream)
private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream)
{
this.stream = stream;

9
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -3,10 +3,9 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif
@ -29,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The stream to decode.
/// </summary>
private readonly Stream stream;
private readonly BufferedReadStream stream;
/// <summary>
/// The prefix buffer.
@ -52,8 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="stream">The stream to read from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(MemoryAllocator memoryAllocator, Stream stream)
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
{
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));

15
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -21,18 +22,16 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>The decoded image.</returns>
Image<TPixel> Decode<TPixel>(Stream stream)
Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <returns>The <see cref="IImageInfo"/>.</returns>
IImageInfo Identify(Stream stream);
IImageInfo Identify(BufferedReadStream stream);
}
}

45
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -14,42 +14,21 @@ namespace SixLabors.ImageSharp.Formats
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public static async Task<IImageInfo> IdentifyAsync(this IImageDecoderInternals decoder, Stream stream)
{
if (stream.CanSeek)
{
return decoder.Identify(stream);
}
using MemoryStream ms = decoder.Configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return decoder.Identify(ms);
}
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream)
=> Task.FromResult(decoder.Identify(stream));
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public static async Task<Image<TPixel>> DecodeAsync<TPixel>(this IImageDecoderInternals decoder, Stream stream)
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(this IImageDecoderInternals decoder, BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
return decoder.Decode<TPixel>(stream);
}
using MemoryStream ms = decoder.Configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return decoder.Decode<TPixel>(ms);
}
=> Task.FromResult(decoder.Decode<TPixel>(stream));
}
}

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
internal struct HuffmanScanBuffer
{
private readonly Stream stream;
private readonly BufferedReadStream stream;
// The entropy encoded code buffer.
private ulong data;
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Whether there is no more good data to pull from the stream for the current mcu.
private bool badData;
public HuffmanScanBuffer(Stream stream)
public HuffmanScanBuffer(BufferedReadStream stream)
{
this.stream = stream;
this.data = 0ul;

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -2,9 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private readonly JpegFrame frame;
private readonly HuffmanTable[] dcHuffmanTables;
private readonly HuffmanTable[] acHuffmanTables;
private readonly Stream stream;
private readonly BufferedReadStream stream;
private readonly JpegComponent[] components;
// The restart interval.
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="successiveHigh">The successive approximation bit high end.</param>
/// <param name="successiveLow">The successive approximation bit low end.</param>
public HuffmanScanDecoder(
Stream stream,
BufferedReadStream stream,
JpegFrame frame,
HuffmanTable[] dcHuffmanTables,
HuffmanTable[] acHuffmanTables,

45
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -13,13 +14,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -27,21 +26,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
using var decoder = new JpegDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -49,23 +53,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
using var decoder = new JpegDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
@ -75,21 +76,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new JpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
}
/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new JpegDecoderCore(configuration, this))
{
return await decoder.IdentifyAsync(stream).ConfigureAwait(false);
}
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
}
}
}

33
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -3,13 +3,12 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -174,7 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, Stream stream)
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream)
{
int value = stream.Read(marker, 0, 2);
@ -206,7 +205,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Stream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ParseStream(stream);
@ -218,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public IImageInfo Identify(Stream stream)
public IImageInfo Identify(BufferedReadStream stream)
{
this.ParseStream(stream, true);
this.InitExifProfile();
@ -234,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
public void ParseStream(Stream stream, bool metadataOnly = false)
public void ParseStream(BufferedReadStream stream, bool metadataOnly = false)
{
this.Metadata = new ImageMetadata();
@ -497,7 +496,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(Stream stream, int remaining)
private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining)
{
// We can only decode JFif identifiers.
if (remaining < JFifMarker.Length)
@ -524,7 +523,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp1Marker(Stream stream, int remaining)
private void ProcessApp1Marker(BufferedReadStream stream, int remaining)
{
const int Exif00 = 6;
if (remaining < Exif00 || this.IgnoreMetadata)
@ -558,7 +557,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp2Marker(Stream stream, int remaining)
private void ProcessApp2Marker(BufferedReadStream stream, int remaining)
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
@ -601,7 +600,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp13Marker(Stream stream, int remaining)
private void ProcessApp13Marker(BufferedReadStream stream, int remaining)
{
if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
{
@ -691,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp14Marker(Stream stream, int remaining)
private void ProcessApp14Marker(BufferedReadStream stream, int remaining)
{
const int MarkerLength = AdobeMarker.Length;
if (remaining < MarkerLength)
@ -720,7 +719,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// </exception>
private void ProcessDefineQuantizationTablesMarker(Stream stream, int remaining)
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{
while (remaining > 0)
{
@ -806,7 +805,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(Stream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@ -907,7 +906,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(Stream stream, int remaining)
private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining)
{
int length = remaining;
@ -974,7 +973,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(Stream stream, int remaining)
private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining)
{
if (remaining != 2)
{
@ -988,7 +987,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="stream">The input stream.</param>
private void ProcessStartOfScanMarker(Stream stream)
private void ProcessStartOfScanMarker(BufferedReadStream stream)
{
if (this.Frame is null)
{
@ -1061,7 +1060,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="stream">The input stream.</param>
/// <returns>The <see cref="ushort"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
private ushort ReadUint16(Stream stream)
private ushort ReadUint16(BufferedReadStream stream)
{
stream.Read(this.markerBuffer, 0, 2);
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);

53
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -13,83 +14,73 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The decoded image.</returns>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(stream);
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
return decoder.Identify(stream);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
return decoder.IdentifyAsync(stream);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
}
}

13
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream currentStream;
private BufferedReadStream currentStream;
/// <summary>
/// The png header.
@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public Size Dimensions => new Size(this.header.Width, this.header.Height);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Stream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var metadata = new ImageMetadata();
@ -224,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <inheritdoc/>
public IImageInfo Identify(Stream stream)
public IImageInfo Identify(BufferedReadStream stream)
{
var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata();
@ -499,7 +499,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="image">The image to decode to.</param>
/// <param name="pngMetadata">The png metadata</param>
private void DecodePixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
private void DecodePixelData<TPixel>(DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
while (this.currentRow < this.header.Height)
@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="image">The current image.</param>
/// <param name="pngMetadata">The png metadata.</param>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
private void DecodeInterlacedPixelData<TPixel>(DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
int pass = 0;
@ -1027,7 +1027,8 @@ namespace SixLabors.ImageSharp.Formats.Png
private bool TryUncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, out string value)
{
using (var memoryStream = new MemoryStream(compressedData.ToArray()))
using (var inflateStream = new ZlibInflateStream(memoryStream))
using (var bufferedStream = new BufferedReadStream(memoryStream))
using (var inflateStream = new ZlibInflateStream(bufferedStream))
{
if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
{

9
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.IO.Compression;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// The inner raw memory stream.
/// </summary>
private readonly Stream innerStream;
private readonly BufferedReadStream innerStream;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream.</param>
public ZlibInflateStream(Stream innerStream)
public ZlibInflateStream(BufferedReadStream innerStream)
: this(innerStream, GetDataNoOp)
{
}
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary>
/// <param name="innerStream">The inner raw stream.</param>
/// <param name="getData">A delegate to get more data from the inner stream.</param>
public ZlibInflateStream(Stream innerStream, Func<int> getData)
public ZlibInflateStream(BufferedReadStream innerStream, Func<int> getData)
{
this.innerStream = innerStream;
this.getData = getData;
@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.currentDataRemaining -= 4;
}
// Initialize the deflate Stream.
// Initialize the deflate BufferedReadStream.
this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
return true;

31
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -3,6 +3,7 @@
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -23,21 +24,26 @@ namespace SixLabors.ImageSharp.Formats.Tga
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
}
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
@ -46,13 +52,14 @@ namespace SixLabors.ImageSharp.Formats.Tga
try
{
return decoder.Decode<TPixel>(stream);
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
// Not reachable, as the previous statement will throw a exception.
return null;
@ -60,17 +67,16 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Identify(stream);
using var bufferedStream = new BufferedReadStream(stream);
return new TgaDecoderCore(configuration, this).Identify(bufferedStream);
}
/// <inheritdoc/>
@ -78,7 +84,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).IdentifyAsync(stream);
using var bufferedStream = new BufferedReadStream(stream);
return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
}
}
}

11
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -3,7 +3,6 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// The stream to decode from.
/// </summary>
private Stream currentStream;
private BufferedReadStream currentStream;
/// <summary>
/// The bitmap decoder options.
@ -78,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(Stream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
try
@ -641,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <inheritdoc />
public IImageInfo Identify(Stream stream)
public IImageInfo Identify(BufferedReadStream stream)
{
this.ReadFileHeader(stream);
return new ImageInfo(
@ -868,9 +867,9 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Reads the tga file header from the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <returns>The image origin.</returns>
private TgaImageOrigin ReadFileHeader(Stream stream)
private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
{
this.currentStream = stream;

94
src/ImageSharp/IO/BufferedReadStream.cs

@ -157,14 +157,38 @@ namespace SixLabors.ImageSharp.IO
return this.ReadToBufferViaCopyFast(buffer, offset, count);
}
#if SUPPORTS_SPAN_STREAM
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int Read(Span<byte> buffer)
{
// Too big for our buffer. Read directly from the stream.
int count = buffer.Length;
if (count > BufferLength)
{
return this.ReadToBufferDirectSlow(buffer);
}
// 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);
}
return this.ReadToBufferViaCopyFast(buffer);
}
#endif
/// <inheritdoc/>
public override void Flush()
{
// Reset the stream position to match reader position.
if (this.readerPosition != this.BaseStream.Position)
Stream baseStream = this.BaseStream;
if (this.readerPosition != baseStream.Position)
{
this.BaseStream.Seek(this.readerPosition, SeekOrigin.Begin);
this.readerPosition = (int)this.BaseStream.Position;
baseStream.Seek(this.readerPosition, SeekOrigin.Begin);
this.readerPosition = (int)baseStream.Position;
}
// Reset to trigger full read on next attempt.
@ -231,9 +255,10 @@ namespace SixLabors.ImageSharp.IO
[MethodImpl(MethodImplOptions.NoInlining)]
private void FillReadBuffer()
{
if (this.readerPosition != this.BaseStream.Position)
Stream baseStream = this.BaseStream;
if (this.readerPosition != baseStream.Position)
{
this.BaseStream.Seek(this.readerPosition, SeekOrigin.Begin);
baseStream.Seek(this.readerPosition, SeekOrigin.Begin);
}
// Read doesn't always guarantee the full returned length so read a byte
@ -242,7 +267,7 @@ namespace SixLabors.ImageSharp.IO
int i;
do
{
i = this.BaseStream.Read(this.readBuffer, n, BufferLength - n);
i = baseStream.Read(this.readBuffer, n, BufferLength - n);
n += i;
}
while (n < BufferLength && i > 0);
@ -250,6 +275,20 @@ namespace SixLabors.ImageSharp.IO
this.readBufferIndex = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopyFast(Span<byte> buffer)
{
int n = this.GetCopyCount(buffer.Length);
// Just straight copy. MemoryStream does the same so should be fast enough.
this.readBuffer.AsSpan(this.readBufferIndex, n).CopyTo(buffer);
this.readerPosition += n;
this.readBufferIndex += n;
return n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count)
{
@ -262,6 +301,15 @@ namespace SixLabors.ImageSharp.IO
return n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopySlow(Span<byte> buffer)
{
// Refill our buffer then copy.
this.FillReadBuffer();
return this.ReadToBufferViaCopyFast(buffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count)
{
@ -271,13 +319,41 @@ namespace SixLabors.ImageSharp.IO
return this.ReadToBufferViaCopyFast(buffer, offset, count);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadToBufferDirectSlow(Span<byte> buffer)
{
// Read to target but don't copy to our read buffer.
Stream baseStream = this.BaseStream;
if (this.readerPosition != baseStream.Position)
{
baseStream.Seek(this.readerPosition, SeekOrigin.Begin);
}
// Read doesn't always guarantee the full returned length so read a byte
// at a time until we get either our count or hit the end of the stream.
int count = buffer.Length;
int n = 0;
int i;
do
{
i = baseStream.Read(buffer.Slice(n, count - n));
n += i;
}
while (n < count && i > 0);
this.Position += n;
return n;
}
[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.readerPosition != this.BaseStream.Position)
Stream baseStream = this.BaseStream;
if (this.readerPosition != baseStream.Position)
{
this.BaseStream.Seek(this.readerPosition, SeekOrigin.Begin);
baseStream.Seek(this.readerPosition, SeekOrigin.Begin);
}
// Read doesn't always guarantee the full returned length so read a byte
@ -286,7 +362,7 @@ namespace SixLabors.ImageSharp.IO
int i;
do
{
i = this.BaseStream.Read(buffer, n + offset, count - n);
i = baseStream.Read(buffer, n + offset, count - n);
n += i;
}
while (n < count && i > 0);

66
src/ImageSharp/Image.FromStream.cs

@ -7,7 +7,6 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration), false);
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration));
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
@ -63,8 +62,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
s => InternalDetectFormatAsync(s, configuration),
false);
s => InternalDetectFormatAsync(s, configuration));
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -663,17 +661,11 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param>
/// <param name="buffer">
/// Whether to buffer the input stream.
/// Short intial reads like <see cref="DetectFormat(Configuration, Stream)"/> do not require
/// the overhead of reading the stream to the buffer. Defaults to <see langword="true"/>.
/// </param>
/// <returns>The <typeparamref name="T"/>.</returns>
private static T WithSeekableStream<T>(
Configuration configuration,
Stream stream,
Func<Stream, T> action,
bool buffer = true)
Func<Stream, T> action)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream));
@ -690,29 +682,15 @@ namespace SixLabors.ImageSharp
stream.Position = 0;
}
if (buffer)
{
using var bufferedStream = new BufferedReadStream(stream);
return action(bufferedStream);
}
return action(stream);
}
// We want to be able to load images from things like HttpContext.Request.Body
using (MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length))
{
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
if (buffer)
{
using var bufferedStream = new BufferedReadStream(memoryStream);
return action(bufferedStream);
}
return action(memoryStream);
}
return action(memoryStream);
}
/// <summary>
@ -722,17 +700,11 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The input stream.</param>
/// <param name="action">The action to perform.</param>
/// <param name="buffer">
/// Whether to buffer the input stream.
/// Short intial reads like <see cref="DetectFormat(Configuration, Stream)"/> do not require
/// the overhead of reading the stream to the buffer. Defaults to <see langword="true"/>.
/// </param>
/// <returns>The <see cref="Task{T}"/>.</returns>
private static async Task<T> WithSeekableStreamAsync<T>(
Configuration configuration,
Stream stream,
Func<Stream, Task<T>> action,
bool buffer = true)
Func<Stream, Task<T>> action)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream));
@ -753,28 +725,14 @@ namespace SixLabors.ImageSharp
stream.Position = 0;
}
if (buffer)
{
using var bufferedStream = new BufferedReadStream(stream);
return await action(bufferedStream).ConfigureAwait(false);
}
return await action(stream).ConfigureAwait(false);
}
using (MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length))
{
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
if (buffer)
{
using var bufferedStream = new BufferedReadStream(memoryStream);
return await action(bufferedStream).ConfigureAwait(false);
}
using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length);
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
return await action(memoryStream).ConfigureAwait(false);
}
return await action(memoryStream).ConfigureAwait(false);
}
}
}

23
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -6,6 +6,7 @@ using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Tests;
using SDSize = System.Drawing.Size;
@ -30,24 +31,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true, Description = "System.Drawing FULL")]
public SDSize JpegSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = System.Drawing.Image.FromStream(memoryStream))
{
return image.Size;
}
}
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = System.Drawing.Image.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "JpegDecoderCore.ParseStream")]
public void ParseStreamPdfJs()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new JpegDecoderCore(Configuration.Default, new Formats.Jpeg.JpegDecoder { IgnoreMetadata = true });
decoder.ParseStream(memoryStream);
decoder.Dispose();
}
using var memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(memoryStream);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true });
decoder.ParseStream(bufferedStream);
decoder.Dispose();
}
// RESULTS (2019 April 23):

20
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -71,16 +72,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public void ParseStream_BasicPropertiesAreCorrect()
{
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms);
// I don't know why these numbers are different. All I know is that the decoder works
// and spectral data is exactly correct also.
// VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31);
}
using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(ms);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(bufferedStream);
// I don't know why these numbers are different. All I know is that the decoder works
// and spectral data is exactly correct also.
// VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31);
}
public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg";

23
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -51,13 +52,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
decoder.ParseStream(ms);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(ms);
decoder.ParseStream(bufferedStream);
var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
VerifyJpeg.SaveSpectralImage(provider, data);
}
var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
VerifyJpeg.SaveSpectralImage(provider, data);
}
[Theory]
@ -74,13 +74,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
using var ms = new MemoryStream(sourceBytes);
using var bufferedStream = new BufferedReadStream(ms);
decoder.ParseStream(bufferedStream);
this.VerifySpectralCorrectnessImpl(provider, imageSharpData);
}
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.VerifySpectralCorrectnessImpl(provider, imageSharpData);
}
private void VerifySpectralCorrectnessImpl<TPixel>(

15
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -8,7 +8,7 @@ using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.IO;
using Xunit;
using Xunit.Abstractions;
@ -192,12 +192,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false)
{
byte[] bytes = TestFile.Create(testFileName).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms, metaDataOnly);
return decoder;
}
using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(ms);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(bufferedStream, metaDataOnly);
return decoder;
}
}
}

36
tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs

@ -143,6 +143,42 @@ namespace SixLabors.ImageSharp.Tests.IO
}
}
[Fact]
public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly()
{
using (MemoryStream stream = this.CreateTestStream())
{
Span<byte> buffer = new byte[2];
byte[] expected = stream.ToArray();
using (var reader = new BufferedReadStream(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 < BufferedReadStream.BufferLength)
{
Assert.Equal(stream.Position, BufferedReadStream.BufferLength);
}
else if (offset >= BufferedReadStream.BufferLength && offset < BufferedReadStream.BufferLength * 2)
{
// We should have advanced to the second chunk now.
Assert.Equal(stream.Position, BufferedReadStream.BufferLength * 2);
}
else
{
// We should have advanced to the third chunk now.
Assert.Equal(stream.Position, BufferedReadStream.BufferLength * 3);
}
}
}
}
}
[Fact]
public void BufferedStreamCanSkip()
{

27
tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs

@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using Moq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -44,11 +42,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder
.Verify(
x => x.Decode<Rgba32>(
this.TopLevelConfiguration,
It.Is<Stream>(x => ((BufferedReadStream)x).BaseStream == this.DataStream)));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, this.DataStream));
}
[Fact]
@ -57,10 +51,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(
x => x.Decode(
this.TopLevelConfiguration,
It.Is<Stream>(x => ((BufferedReadStream)x).BaseStream == this.DataStream)));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream));
}
[Fact]
@ -90,9 +81,9 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.Throws<System.IO.FileNotFoundException>(
() =>
{
Image.Load<Rgba32>(this.TopLevelConfiguration, Guid.NewGuid().ToString());
});
{
Image.Load<Rgba32>(this.TopLevelConfiguration, Guid.NewGuid().ToString());
});
}
[Fact]
@ -100,9 +91,9 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.Throws<ArgumentNullException>(
() =>
{
Image.Load<Rgba32>(this.TopLevelConfiguration, (string)null);
});
{
Image.Load<Rgba32>(this.TopLevelConfiguration, (string)null);
});
}
}
}

13
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs

@ -2,9 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using Moq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -55,10 +54,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load<Rgba32>(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(
x => x.Decode<Rgba32>(
this.TopLevelConfiguration,
It.Is<Stream>(x => ((BufferedReadStream)x).BaseStream == stream)));
this.localDecoder.Verify(x => x.Decode<Rgba32>(this.TopLevelConfiguration, stream));
}
[Fact]
@ -68,10 +64,7 @@ namespace SixLabors.ImageSharp.Tests
var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object);
Assert.NotNull(img);
this.localDecoder.Verify(
x => x.Decode(
this.TopLevelConfiguration,
It.Is<Stream>(x => ((BufferedReadStream)x).BaseStream == stream)));
this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream));
}
[Fact]

Loading…
Cancel
Save