From 089b41e2a976d19f1617aabfa23f8b042221a12e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 Jun 2020 23:47:52 +0100 Subject: [PATCH] Use pooled stream for async decode/identify --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 3 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 4 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 3 +- .../Formats/Jpeg/JpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 23 ++------- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 3 +- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 3 +- .../IO/FixedCapacityPooledMemoryStream.cs | 50 +++++++++++++++++++ src/ImageSharp/Image.FromStream.cs | 9 ++-- .../TestUtilities/AsyncStreamWrapper.cs | 2 +- 11 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 26f6c5080..16da086c9 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -83,11 +83,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - return await new BmpDecoderCore(configuration, this).IdentifyAsync(stream).ConfigureAwait(false); + return new BmpDecoderCore(configuration, this).IdentifyAsync(stream); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index f5b576f17..e37144bd5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -9,6 +9,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -257,7 +258,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing image data. public async Task IdentifyAsync(Stream stream) { - using (var ms = new MemoryStream()) + using (var ms = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index fbfbee2e4..5f4fdd0fa 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -77,12 +77,12 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - return await decoder.IdentifyAsync(stream).ConfigureAwait(false); + return decoder.IdentifyAsync(stream); } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 63eab072b..8f8426780 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -199,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (var ms = new MemoryStream()) + using (var ms = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ed5c17faa..af1a705d4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -266,7 +266,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - using (var ms = new MemoryStream()) + using (var ms = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 2eeb1ac80..a6a040789 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -9,25 +9,8 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png { /// - /// Encoder for generating an image out of a png encoded stream. + /// Decoder for generating an image out of a png encoded stream. /// - /// - /// At the moment the following features are supported: - /// - /// Filters: all filters are supported. - /// - /// - /// Pixel formats: - /// - /// RGBA (True color) with alpha (8 bit). - /// RGB (True color) without alpha (8 bit). - /// grayscale with alpha (8 bit). - /// grayscale without alpha (8 bit). - /// Palette Index with alpha (8 bit). - /// Palette Index without alpha (8 bit). - /// - /// - /// public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector { /// @@ -97,10 +80,10 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream) { var decoder = new PngDecoderCore(configuration, this); - return await decoder.IdentifyAsync(stream).ConfigureAwait(false); + return decoder.IdentifyAsync(stream); } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 0aec984cc..bb8589de4 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -277,7 +278,7 @@ namespace SixLabors.ImageSharp.Formats.Png } else { - using (var ms = new MemoryStream()) + using (var ms = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 66cb0ed18..ae8946173 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -689,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - using (var ms = new MemoryStream()) + using (var ms = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; diff --git a/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs b/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs new file mode 100644 index 000000000..878fcc53a --- /dev/null +++ b/src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.IO; + +namespace SixLabors.ImageSharp.IO +{ + /// + /// A memory stream constructed from a pooled buffer of known length. + /// + internal sealed class FixedCapacityPooledMemoryStream : MemoryStream + { + private readonly byte[] buffer; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The length of the stream buffer to rent. + public FixedCapacityPooledMemoryStream(long length) + : this(RentBuffer(length)) => this.Length = length; + + private FixedCapacityPooledMemoryStream(byte[] buffer) + : base(buffer) => this.buffer = buffer; + + /// + public override long Length { get; } + + /// + protected override void Dispose(bool disposing) + { + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + ArrayPool.Shared.Return(this.buffer); + } + + base.Dispose(disposing); + } + } + + // In the extrememly unlikely event someone ever gives us a stream + // with length longer than int.MaxValue then we'll use something else. + private static byte[] RentBuffer(long length) => ArrayPool.Shared.Rent((int)length); + } +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 5c64ae559..499f5ac19 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -7,6 +7,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -674,10 +675,7 @@ namespace SixLabors.ImageSharp } // We want to be able to load images from things like HttpContext.Request.Body - // TODO: Should really find a nice way to use a pool for these. - // Investigate readonly version of the linked implementation. - // https://github.com/mgravell/Pipelines.Sockets.Unofficial/compare/mgravell:24482d4...mgravell:6740ea4 - using (var memoryStream = new MemoryStream()) + using (var memoryStream = new FixedCapacityPooledMemoryStream(stream.Length)) { stream.CopyTo(memoryStream); memoryStream.Position = 0; @@ -713,8 +711,7 @@ namespace SixLabors.ImageSharp return await action(stream).ConfigureAwait(false); } - // TODO: See above comment. - using (var memoryStream = new MemoryStream()) + using (var memoryStream = new FixedCapacityPooledMemoryStream(stream.Length)) { await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs index 2000c6e0c..6a05ce0bc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities public override bool CanWrite => this.inner.CanWrite; - public override long Length => throw new NotSupportedException("The stream is not seekable."); + public override long Length => this.inner.Length; public override long Position {