From 5537435bb01930de565186ad0f89ba5bfdedda6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Jul 2020 13:31:36 +0100 Subject: [PATCH] Use explicit stream instance in core decoders. --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 35 ++++--- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 10 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 38 +++++--- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 8 +- src/ImageSharp/Formats/Gif/LzwDecoder.cs | 9 +- .../Formats/IImageDecoderInternals.cs | 15 ++- .../Formats/ImageDecoderUtilities.cs | 45 +++------ .../Components/Decoder/HuffmanScanBuffer.cs | 6 +- .../Components/Decoder/HuffmanScanDecoder.cs | 6 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 45 ++++----- .../Formats/Jpeg/JpegDecoderCore.cs | 33 ++++--- src/ImageSharp/Formats/Png/PngDecoder.cs | 53 +++++------ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 +-- .../Formats/Png/Zlib/ZlibInflateStream.cs | 9 +- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 31 +++--- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 11 +-- src/ImageSharp/IO/BufferedReadStream.cs | 94 +++++++++++++++++-- src/ImageSharp/Image.FromStream.cs | 66 +++---------- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 23 ++--- .../Formats/Jpg/JpegDecoderTests.cs | 20 ++-- .../Formats/Jpg/SpectralJpegTests.cs | 23 +++-- .../Formats/Jpg/Utils/JpegFixture.cs | 15 +-- .../IO/BufferedReadStreamTests.cs | 36 +++++++ ...d_FileSystemPath_PassLocalConfiguration.cs | 27 ++---- ....Load_FromStream_PassLocalConfiguration.cs | 13 +-- 25 files changed, 362 insertions(+), 322 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 16da086c9..7e8ac0721 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/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; /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -38,19 +39,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp try { - return await decoder.DecodeAsync(stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.Decode(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); } } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + /// - public Image Decode(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel + public async Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -58,28 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp try { - return decoder.Decode(stream); + using var bufferedStream = new BufferedReadStream(stream); + return await decoder.DecodeAsync(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); } } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream) + => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); /// 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); } /// @@ -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); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 4b14061cf..ea8fd11a8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/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 /// /// The stream to decode from. /// - private Stream stream; + private BufferedReadStream stream; /// /// The metadata. @@ -120,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); /// - public Image Decode(Stream stream) + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { try @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - 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 /// /// 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. - 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; diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 5f4fdd0fa..2a5fde6ac 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/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; /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); try { - return await decoder.DecodeAsync(stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.Decode(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; } } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + /// - public Image Decode(Configuration configuration, Stream stream) + public async Task> DecodeAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); try { - return decoder.Decode(stream); + using var bufferedStream = new BufferedReadStream(stream); + return await decoder.DecodeAsync(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; } } + /// + public async Task DecodeAsync(Configuration configuration, Stream stream) + => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + /// 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); } /// @@ -82,13 +94,9 @@ namespace SixLabors.ImageSharp.Formats.Gif Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - return decoder.IdentifyAsync(stream); - } - /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.IdentifyAsync(bufferedStream); + } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index e4c98799b..78ffee8bd 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The currently loaded stream. /// - private Stream stream; + private BufferedReadStream stream; /// /// The global color table. @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; /// - public Image Decode(Stream stream) + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { Image image = null; @@ -158,7 +158,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - 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 /// /// The stream containing image data. - private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream) + private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) { this.stream = stream; diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 6a975951c..9eaa55566 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/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 /// /// The stream to decode. /// - private readonly Stream stream; + private readonly BufferedReadStream stream; /// /// The prefix buffer. @@ -52,8 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The to use for buffer allocations. /// The stream to read from. - /// is null. - public LzwDecoder(MemoryAllocator memoryAllocator, Stream stream) + /// is null. + public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream) { this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs index 3ab912353..33748bf24 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/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 /// /// The pixel format. /// The stream, where the image should be decoded from. Cannot be null. - /// - /// is null. - /// + /// is null. /// The decoded image. - Image Decode(Stream stream) + Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel; /// /// Reads the raw image information from the specified stream. /// - /// The containing image data. + /// The containing image data. /// The . - IImageInfo Identify(Stream stream); + IImageInfo Identify(BufferedReadStream stream); } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 6bb9116cd..9d1639a09 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/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. /// /// The decoder. - /// The containing image data. - public static async Task 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); - } + /// The containing image data. + /// is null. + /// A representing the asynchronous operation. + public static Task IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) + => Task.FromResult(decoder.Identify(stream)); /// /// Decodes the image from the specified stream. /// /// The pixel format. /// The decoder. - /// The stream, where the image should be decoded from. Cannot be null. - /// - /// is null. - /// - /// The decoded image. - public static async Task> DecodeAsync(this IImageDecoderInternals decoder, Stream stream) + /// The containing image data. + /// A representing the asynchronous operation. + public static Task> DecodeAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) where TPixel : unmanaged, IPixel - { - if (stream.CanSeek) - { - return decoder.Decode(stream); - } - - using MemoryStream ms = decoder.Configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return decoder.Decode(ms); - } + => Task.FromResult(decoder.Decode(stream)); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs index 44f3aa563..774780170 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs +++ b/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 /// 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; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 52ea65dd8..d6c16f826 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/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 /// The successive approximation bit high end. /// The successive approximation bit low end. public HuffmanScanDecoder( - Stream stream, + BufferedReadStream stream, JpegFrame frame, HuffmanTable[] dcHuffmanTables, HuffmanTable[] acHuffmanTables, diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 2d2d7fb56..c5332acb5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/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 /// public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// + /// public bool IgnoreMetadata { get; set; } /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { 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(stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.Decode(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; } } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + /// - public Image Decode(Configuration configuration, Stream stream) + public async Task> DecodeAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { 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(stream); + using var bufferedStream = new BufferedReadStream(stream); + return await decoder.DecodeAsync(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; } } - /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); - /// public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(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); } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream) + public Task 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); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 48501ddf6..2956d2c11 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/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 /// The buffer to read file markers to /// The input stream /// The - 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 } /// - public Image Decode(Stream stream) + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { this.ParseStream(stream); @@ -218,7 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - 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 /// /// The input stream /// Whether to decode metadata only. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// Thrown if the tables do not match the header /// - 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 /// The remaining bytes in the segment block. /// The current frame marker. /// Whether to parse metadata only - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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 /// /// The input stream. /// The remaining bytes in the segment block. - 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). /// /// The input stream. - private void ProcessStartOfScanMarker(Stream stream) + private void ProcessStartOfScanMarker(BufferedReadStream stream) { if (this.Frame is null) { @@ -1061,7 +1060,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The input stream. /// The [MethodImpl(InliningOptions.ShortMethod)] - private ushort ReadUint16(Stream stream) + private ushort ReadUint16(BufferedReadStream stream) { stream.Read(this.markerBuffer, 0, 2); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index a6a040789..9eb927784 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/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 /// public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// + /// public bool IgnoreMetadata { get; set; } - /// - /// Decodes the image from the specified stream to the . - /// - /// The pixel format. - /// The configuration for the image. - /// The containing image data. - /// The decoded image. - public async Task> DecodeAsync(Configuration configuration, Stream stream) + /// + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); try { - return await decoder.DecodeAsync(stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.Decode(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; } } - /// - /// Decodes the image from the specified stream to the . - /// - /// The pixel format. - /// The configuration for the image. - /// The containing image data. - /// The decoded image. - public Image Decode(Configuration configuration, Stream stream) + /// + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + /// + public async Task> DecodeAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); try { - return decoder.Decode(stream); + using var bufferedStream = new BufferedReadStream(stream); + return await decoder.DecodeAsync(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; } } + /// + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + /// 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); } /// public Task 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); } - - /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e2b0e50fc..89fa4e63d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The stream to decode from. /// - private Stream currentStream; + private BufferedReadStream currentStream; /// /// The png header. @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Png public Size Dimensions => new Size(this.header.Width, this.header.Height); /// - public Image Decode(Stream stream) + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); @@ -224,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - 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 /// The compressed pixel data stream. /// The image to decode to. /// The png metadata - private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) + private void DecodePixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { while (this.currentRow < this.header.Height) @@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The compressed pixel data stream. /// The current image. /// The png metadata. - private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) + private void DecodeInterlacedPixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { int pass = 0; @@ -1027,7 +1027,8 @@ namespace SixLabors.ImageSharp.Formats.Png private bool TryUncompressTextData(ReadOnlySpan 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)) { diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 07316d37b..52ef0e85b 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/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 /// /// The inner raw memory stream. /// - private readonly Stream innerStream; + private readonly BufferedReadStream innerStream; /// /// 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 class. /// /// The inner raw stream. - public ZlibInflateStream(Stream innerStream) + public ZlibInflateStream(BufferedReadStream innerStream) : this(innerStream, GetDataNoOp) { } @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The inner raw stream. /// A delegate to get more data from the inner stream. - public ZlibInflateStream(Stream innerStream, Func getData) + public ZlibInflateStream(BufferedReadStream innerStream, Func 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; diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 06b9ab605..3d9b9a3d2 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/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 { /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -23,21 +24,26 @@ namespace SixLabors.ImageSharp.Formats.Tga try { - return await decoder.DecodeAsync(stream).ConfigureAwait(false); + using var bufferedStream = new BufferedReadStream(stream); + return decoder.Decode(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; } } + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + /// - public Image Decode(Configuration configuration, Stream stream) + public async Task> DecodeAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -46,13 +52,14 @@ namespace SixLabors.ImageSharp.Formats.Tga try { - return decoder.Decode(stream); + using var bufferedStream = new BufferedReadStream(stream); + return await decoder.DecodeAsync(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 } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream) + => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); /// 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); } /// @@ -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); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 3f6d721f6..7cd83fedb 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/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 /// /// The stream to decode from. /// - private Stream currentStream; + private BufferedReadStream currentStream; /// /// The bitmap decoder options. @@ -78,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tga public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); /// - public Image Decode(Stream stream) + public Image Decode(BufferedReadStream stream) where TPixel : unmanaged, IPixel { try @@ -641,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - 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 /// /// Reads the tga file header from the stream. /// - /// The containing image data. + /// The containing image data. /// The image origin. - private TgaImageOrigin ReadFileHeader(Stream stream) + private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) { this.currentStream = stream; diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index 269c1aa8e..816626374 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -157,14 +157,38 @@ namespace SixLabors.ImageSharp.IO return this.ReadToBufferViaCopyFast(buffer, offset, count); } +#if SUPPORTS_SPAN_STREAM + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(Span 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 + /// 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 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 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 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); diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index e882bf2f8..beec0b188 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/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 /// The stream is not readable. /// The format type or null if none found. public static IImageFormat DetectFormat(Configuration configuration, Stream stream) - => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration), false); + => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); /// /// 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)); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -663,17 +661,11 @@ namespace SixLabors.ImageSharp /// The configuration. /// The input stream. /// The action to perform. - /// - /// Whether to buffer the input stream. - /// Short intial reads like do not require - /// the overhead of reading the stream to the buffer. Defaults to . - /// /// The . private static T WithSeekableStream( Configuration configuration, Stream stream, - Func action, - bool buffer = true) + Func 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); } /// @@ -722,17 +700,11 @@ namespace SixLabors.ImageSharp /// The configuration. /// The input stream. /// The action to perform. - /// - /// Whether to buffer the input stream. - /// Short intial reads like do not require - /// the overhead of reading the stream to the buffer. Defaults to . - /// /// The . private static async Task WithSeekableStreamAsync( Configuration configuration, Stream stream, - Func> action, - bool buffer = true) + Func> 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); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index dfedf3d89..ef098e263 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/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): diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index e69ba98f9..0694a0855 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/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"; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index fad2f06b1..1d200592a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/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( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 983faddf1..96d85fd8e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/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; } } } diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs index c9ace8df2..d08d5adef 100644 --- a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs +++ b/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 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() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 813c68d4c..9d4ffdace 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/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(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder - .Verify( - x => x.Decode( - this.TopLevelConfiguration, - It.Is(x => ((BufferedReadStream)x).BaseStream == this.DataStream))); + this.localDecoder.Verify(x => x.Decode(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(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( () => - { - Image.Load(this.TopLevelConfiguration, Guid.NewGuid().ToString()); - }); + { + Image.Load(this.TopLevelConfiguration, Guid.NewGuid().ToString()); + }); } [Fact] @@ -100,9 +91,9 @@ namespace SixLabors.ImageSharp.Tests { Assert.Throws( () => - { - Image.Load(this.TopLevelConfiguration, (string)null); - }); + { + Image.Load(this.TopLevelConfiguration, (string)null); + }); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs index aa3d50eae..c7737ef8b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ b/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(this.TopLevelConfiguration, stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify( - x => x.Decode( - this.TopLevelConfiguration, - It.Is(x => ((BufferedReadStream)x).BaseStream == stream))); + this.localDecoder.Verify(x => x.Decode(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(x => ((BufferedReadStream)x).BaseStream == stream))); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); } [Fact]