From 3a809ec4c5f77ba1c470897d0720002d2cfde3a1 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 3 May 2020 13:14:58 +0100 Subject: [PATCH 01/22] Add Async APIs to IImageEncoder and IImageDecoder --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 32 +++++++++++++++ src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 41 ++++++++++++++++++- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 13 +++++- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 21 +++++++++- src/ImageSharp/Formats/Gif/GifDecoder.cs | 35 ++++++++++++++++ src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 35 +++++++++++++++- src/ImageSharp/Formats/Gif/GifEncoder.cs | 9 ++++ src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 20 ++++++++- src/ImageSharp/Formats/IImageDecoder.cs | 21 ++++++++++ src/ImageSharp/Formats/IImageEncoder.cs | 12 +++++- src/ImageSharp/Formats/IImageInfoDetector.cs | 13 +++++- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 38 +++++++++++++++++ .../Formats/Jpeg/JpegDecoderCore.cs | 34 ++++++++++++++- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 23 ++++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 3 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 38 +++++++++++++++++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 41 ++++++++++++++++++- src/ImageSharp/Formats/Png/PngEncoder.cs | 16 ++++++++ src/ImageSharp/Formats/Png/PngEncoderCore.cs | 20 ++++++++- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 35 ++++++++++++++++ src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 38 ++++++++++++++++- src/ImageSharp/Formats/Tga/TgaEncoder.cs | 10 ++++- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 19 ++++++++- tests/ImageSharp.Tests/TestFormat.cs | 17 +++++++- .../ReferenceCodecs/MagickReferenceDecoder.cs | 8 +++- .../SystemDrawingReferenceDecoder.cs | 12 +++++- .../SystemDrawingReferenceEncoder.cs | 15 ++++++- .../Tests/TestImageProviderTests.cs | 32 ++++++++++++++- 28 files changed, 623 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 6547fe76a..2699331cc 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -27,6 +28,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; + /// + public async Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new BmpDecoderCore(configuration, this); + + try + { + return await decoder.DecodeAsync(stream).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); + } + } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel @@ -50,6 +71,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// 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 IImageInfo Identify(Configuration configuration, Stream stream) { @@ -57,5 +81,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp return new BmpDecoderCore(configuration, this).Identify(stream); } + + /// + public async Task IdentifyAsync(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return await new BmpDecoderCore(configuration, this).IdentifyAsync(stream).ConfigureAwait(false); + } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 0661f3145..b5ae055a9 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -7,6 +7,7 @@ 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.Memory; using SixLabors.ImageSharp.Metadata; @@ -130,8 +131,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// is null. /// /// The decoded image. - public Image Decode(Stream stream) + public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel + { + // cheat for now do async copy of the stream into memory stream and use the sync version + // we should use an array pool backed memorystream implementation + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } + } + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The pixel format. + /// The stream, where the image should be + /// decoded from. Cannot be null (Nothing in Visual Basic). + /// + /// is null. + /// + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel { try { @@ -218,6 +243,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public async Task IdentifyAsync(Stream stream) + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } + } + /// /// Returns the y- value based on the given height. /// diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index fbc94a73f..52b87ef27 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -39,5 +40,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream); + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index cc07c4d87..3e10eedbb 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -5,7 +5,7 @@ using System; using System.Buffers; using System.IO; using System.Runtime.InteropServices; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; @@ -97,8 +97,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index faa2498d1..e5de1c028 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -24,6 +25,27 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; + /// + public async Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + var decoder = new GifDecoderCore(configuration, this); + + try + { + return await decoder.DecodeAsync(stream).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); + + // Not reachable, as the previous statement will throw a exception. + return null; + } + } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel @@ -54,7 +76,20 @@ namespace SixLabors.ImageSharp.Formats.Gif return decoder.Identify(stream); } + + /// + public async Task IdentifyAsync(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new GifDecoderCore(configuration, this); + return await decoder.IdentifyAsync(stream).ConfigureAwait(false); + } + /// 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/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 81aa55695..fdac0e2ae 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -6,7 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -103,8 +103,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The stream containing image data. /// The decoded image - public Image Decode(Stream stream) + public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } + } + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The stream containing image data. + /// The decoded image + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel { Image image = null; ImageFrame previousFrame = null; @@ -163,6 +180,20 @@ namespace SixLabors.ImageSharp.Formats.Gif return image; } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public async Task IdentifyAsync(Stream stream) + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 762fc03fc..8d4c33eff 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -38,5 +39,13 @@ namespace SixLabors.ImageSharp.Formats.Gif var encoder = new GifEncoderCore(image.GetConfiguration(), this); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new GifEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream); + } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 34c92a180..46fd7e16a 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -74,8 +75,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 4f17f6729..cc5a87f07 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -30,5 +31,25 @@ namespace SixLabors.ImageSharp.Formats /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The configuration for the image. + /// The containing image data. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) + Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an . + /// + /// The configuration for the image. + /// The containing image data. + /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) + Task DecodeAsync(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 01478eb3e..f4d9b2793 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -19,5 +20,14 @@ namespace SixLabors.ImageSharp.Formats /// The to encode the image data to. void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel; + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 2bd33c616..b231e6777 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats { @@ -17,5 +18,13 @@ namespace SixLabors.ImageSharp.Formats /// The containing image data. /// The object IImageInfo Identify(Configuration configuration, Stream stream); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The configuration for the image. + /// The containing image data. + /// The object + Task IdentifyAsync(Configuration configuration, Stream stream); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 97f455c6f..09bf77022 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -17,6 +18,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public bool IgnoreMetadata { get; set; } + /// + public async Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + using var decoder = new JpegDecoderCore(configuration, this); + try + { + return await decoder.DecodeAsync(stream); + } + 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); + + // Not reachable, as the previous statement will throw a exception. + return null; + } + } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel @@ -43,6 +66,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 IImageInfo Identify(Configuration configuration, Stream stream) { @@ -53,5 +80,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return decoder.Identify(stream); } } + + /// + public async 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); + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a754bfd2e..2f9495267 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -6,6 +6,7 @@ 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; @@ -218,8 +219,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The stream, where the image should be. /// The decoded image. - public Image Decode(Stream stream) + public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } + } + + /// + /// Decodes the image from the specified and sets the data to image. + /// + /// The pixel format. + /// The stream, where the image should be. + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel { this.ParseStream(stream); this.InitExifProfile(); @@ -229,6 +247,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return this.PostProcessIntoImage(); } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public async Task IdentifyAsync(Stream stream) + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 9f3d04a8a..e87f9ce75 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -35,5 +36,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public async Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new JpegEncoderCore(this); + + // this hack has to be be here because JpegEncoderCore is unsafe + using (var ms = new MemoryStream()) + { + encoder.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 21bf538ec..44f9ba1df 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -194,7 +195,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The image to write from. /// The stream to write to. public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index b2e243997..b33a0b1dd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -34,6 +35,33 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoderCore(configuration, this); + + try + { + return await decoder.DecodeAsync(stream).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); + + // Not reachable, as the previous statement will throw a exception. + return null; + } + } + /// /// Decodes the image from the specified stream to the . /// @@ -68,7 +96,17 @@ namespace SixLabors.ImageSharp.Formats.Png return decoder.Identify(stream); } + /// + public async Task IdentifyAsync(Configuration configuration, Stream stream) + { + var decoder = new PngDecoderCore(configuration, this); + return await decoder.IdentifyAsync(stream).ConfigureAwait(false); + } + /// 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 d6ba08895..f610f5750 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,7 +9,7 @@ using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; @@ -149,8 +149,31 @@ namespace SixLabors.ImageSharp.Formats.Png /// Thrown if the image is larger than the maximum allowable size. /// /// The decoded image. - public Image Decode(Stream stream) + public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } + } + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The stream containing image data. + /// + /// Thrown if the stream does not contain and end chunk. + /// + /// + /// Thrown if the image is larger than the maximum allowable size. + /// + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata(); @@ -240,6 +263,20 @@ namespace SixLabors.ImageSharp.Formats.Png } } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public async Task IdentifyAsync(Stream stream) + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index bee021550..a2c2ca100 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -56,5 +57,20 @@ namespace SixLabors.ImageSharp.Formats.Png encoder.Encode(image, stream); } } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public async Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) + { + await encoder.EncodeAsync(image, stream); + } + } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c15038847..1c8696fc1 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -7,6 +7,7 @@ using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -131,8 +132,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index abfaba629..5cd7ca7b0 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -12,6 +13,29 @@ namespace SixLabors.ImageSharp.Formats.Tga /// public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector { + /// + public async Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new TgaDecoderCore(configuration, this); + + try + { + return await decoder.DecodeAsync(stream).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); + + // Not reachable, as the previous statement will throw a exception. + return null; + } + } + /// public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel @@ -38,6 +62,9 @@ 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 IImageInfo Identify(Configuration configuration, Stream stream) { @@ -45,5 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tga return new TgaDecoderCore(configuration, this).Identify(stream); } + + /// + public Task IdentifyAsync(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + return new TgaDecoderCore(configuration, this).IdentifyAsync(stream); + } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index ead053572..808139e59 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -5,7 +5,7 @@ using System; using System.Buffers; using System.IO; using System.Runtime.CompilerServices; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -88,8 +88,28 @@ namespace SixLabors.ImageSharp.Formats.Tga /// is null. /// /// The decoded image. - public Image Decode(Stream stream) + public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } + } + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The stream, where the image should be decoded from. Cannot be null. + /// + /// is null. + /// + /// The decoded image. + public Image Decode(Stream stream) + where TPixel : unmanaged, IPixel { try { @@ -650,6 +670,20 @@ namespace SixLabors.ImageSharp.Formats.Tga } } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + public async Task IdentifyAsync(Stream stream) + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } + } + /// /// Reads the raw image information from the specified stream. /// diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index 6280b2ae6..058dd3559 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -2,7 +2,7 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -30,5 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tga var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); } + + /// + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream); + } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index aee3a26cc..3b16048f3 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -5,7 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -55,6 +55,23 @@ namespace SixLabors.ImageSharp.Formats.Tga this.compression = options.Compression; } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public async Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } + } + /// /// Encodes the image to the specified stream from the . /// diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index f3f4daa55..c5a80cfb8 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests } } - public class TestDecoder : ImageSharp.Formats.IImageDecoder + public class TestDecoder : IImageDecoder { private TestFormat testFormat; @@ -219,9 +219,15 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } + public Task> DecodeAsync(Configuration config, Stream stream) + where TPixel : unmanaged, IPixel + => Task.FromResult(this.Decode(config, stream)); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); } public class TestEncoder : ImageSharp.Formats.IImageEncoder @@ -242,6 +248,13 @@ namespace SixLabors.ImageSharp.Tests { // TODO record this happened so we can verify it. } + + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + // TODO record this happened so we can verify it. + return Task.CompletedTask; + } } public struct TestPixelForAgnosticDecode : IPixel diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index a146e260c..f013ccc91 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -5,7 +5,7 @@ using System; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - +using System.Threading.Tasks; using ImageMagick; using SixLabors.ImageSharp.Advanced; @@ -49,6 +49,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } + public Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => Task.FromResult(this.Decode(configuration, stream)); + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { @@ -80,5 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 05a1fbf84..06035b59f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -2,7 +2,8 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; - +using System.Threading.Tasks; +using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -13,6 +14,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); + public Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => Task.FromResult(this.Decode(configuration, stream)); + public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { @@ -43,6 +48,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } + public Task IdentifyAsync(Configuration configuration, Stream stream) + => Task.FromResult(this.Identify(configuration, stream)); + public IImageInfo Identify(Configuration configuration, Stream stream) { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) @@ -53,5 +61,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 34bec526b..0f5300814 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. using System.Drawing.Imaging; using System.IO; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -30,5 +30,16 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs sdBitmap.Save(stream, this.imageFormat); } } + + public Task EncodeAsync(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) + { + sdBitmap.Save(stream, this.imageFormat); + } + + return Task.CompletedTask; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 0390457a0..ff3dc160b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Concurrent; using System.IO; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -349,6 +349,9 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary InvocationCountsAsync = + new ConcurrentDictionary(); + private static readonly object Monitor = new object(); private string callerName; @@ -368,15 +371,27 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } + public Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + InvocationCountsAsync[this.callerName]++; + return Task.FromResult(new Image(42, 42)); + } + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; + internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; + InvocationCountsAsync[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); } private class TestDecoderWithParameters : IImageDecoder @@ -384,6 +399,9 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary InvocationCountsAsync = + new ConcurrentDictionary(); + private static readonly object Monitor = new object(); private string callerName; @@ -407,15 +425,27 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } + public Task> DecodeAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + InvocationCountsAsync[this.callerName]++; + return Task.FromResult(new Image(42, 42)); + } + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; + internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; + InvocationCountsAsync[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + + public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); } } } From ad02e0bbb637f3a18a9296948159d242c86c315c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 3 May 2020 14:54:32 +0100 Subject: [PATCH 02/22] Save async tests --- .../Advanced/AdvancedImageExtensions.cs | 56 ++++++++- src/ImageSharp/Advanced/IImageVisitor.cs | 16 +++ src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 20 ++- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 15 ++- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 30 +++-- .../Formats/Jpeg/JpegDecoderCore.cs | 30 +++-- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 17 ++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 30 +++-- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 15 ++- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 30 +++-- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 15 ++- src/ImageSharp/Image.cs | 33 ++++- src/ImageSharp/ImageExtensions.cs | 57 ++++----- src/ImageSharp/Image{TPixel}.cs | 9 ++ .../Image/ImageTests.SaveAsync.cs | 109 ++++++++++++++++ .../TestUtilities/AsyncOnlyStream.cs | 117 ++++++++++++++++++ 16 files changed, 511 insertions(+), 88 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index f4e9f3042..c845cfc4f 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -2,9 +2,13 @@ // Licensed under the GNU Affero General Public License, Version 3. using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Runtime.InteropServices; - +using System.Text; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -15,6 +19,47 @@ namespace SixLabors.ImageSharp.Advanced /// public static class AdvancedImageExtensions { + /// + /// For a given path find the best encoder to use + /// + /// The source. + /// The Path + /// The matching encoder. + public static IImageEncoder FindEncoded(this Image source, string path) + { + Guard.NotNull(path, nameof(path)); + + string ext = Path.GetExtension(path); + IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); + if (format is null) + { + var sb = new StringBuilder(); + sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); + foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) + { + sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); + } + + throw new NotSupportedException(sb.ToString()); + } + + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + + if (encoder is null) + { + var sb = new StringBuilder(); + sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); + foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + { + sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); + } + + throw new NotSupportedException(sb.ToString()); + } + + return encoder; + } + /// /// Accepts a to implement a double-dispatch pattern in order to /// apply pixel-specific operations on non-generic instances @@ -24,6 +69,15 @@ namespace SixLabors.ImageSharp.Advanced public static void AcceptVisitor(this Image source, IImageVisitor visitor) => source.Accept(visitor); + /// + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances + /// + /// The source. + /// The visitor. + public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) + => source.AcceptAsync(visitor); + /// /// Gets the configuration for the image. /// diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index 50e6337e5..fa7b8e2f1 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the GNU Affero General Public License, Version 3. +using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Advanced @@ -19,4 +20,19 @@ namespace SixLabors.ImageSharp.Advanced void Visit(Image image) where TPixel : unmanaged, IPixel; } + + /// + /// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations + /// on non-generic instances. + /// + public interface IImageVisitorAsync + { + /// + /// Provides a pixel-specific implementation for a given operation. + /// + /// The image. + /// The pixel type. + Task VisitAsync(Image image) + where TPixel : unmanaged, IPixel; + } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index b5ae055a9..7db6fea26 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -134,13 +134,21 @@ namespace SixLabors.ImageSharp.Formats.Bmp public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel { - // cheat for now do async copy of the stream into memory stream and use the sync version - // we should use an array pool backed memorystream implementation - using (var ms = new MemoryStream()) + // if we can seek then we arn't in a context that errors on async operations + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Decode(ms); + return this.Decode(stream); + } + else + { + // cheat for now do async copy of the stream into memory stream and use the sync version + // we should use an array pool backed memorystream implementation + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 3e10eedbb..93727bb6e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -100,11 +100,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); + this.Encode(image, stream); + } + else + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index fdac0e2ae..c79d006df 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -106,11 +106,18 @@ namespace SixLabors.ImageSharp.Formats.Gif public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Decode(ms); + return this.Decode(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } } } @@ -186,11 +193,18 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The containing image data. public async Task IdentifyAsync(Stream stream) { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Identify(ms); + return this.Identify(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2f9495267..b694c02bb 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -222,11 +222,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Decode(ms); + return this.Decode(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } } } @@ -253,11 +260,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The containing image data. public async Task IdentifyAsync(Stream stream) { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Identify(ms); + return this.Identify(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index e87f9ce75..1838b8d6d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -48,12 +48,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { var encoder = new JpegEncoderCore(this); - // this hack has to be be here because JpegEncoderCore is unsafe - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - encoder.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); + encoder.Encode(image, stream); + } + else + { + // this hack has to be be here because JpegEncoderCore is unsafe + using (var ms = new MemoryStream()) + { + encoder.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f610f5750..713e5c651 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -152,11 +152,18 @@ namespace SixLabors.ImageSharp.Formats.Png public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Decode(ms); + return this.Decode(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } } } @@ -269,11 +276,18 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing image data. public async Task IdentifyAsync(Stream stream) { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Identify(ms); + return this.Identify(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 1c8696fc1..a3b7ab23d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -135,11 +135,18 @@ namespace SixLabors.ImageSharp.Formats.Png public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); + this.Encode(image, stream); + } + else + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 808139e59..f70d7ca24 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -91,11 +91,18 @@ namespace SixLabors.ImageSharp.Formats.Tga public async Task> DecodeAsync(Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Decode(ms); + return this.Decode(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Decode(ms); + } } } @@ -676,11 +683,18 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The containing image data. public async Task IdentifyAsync(Stream stream) { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - await stream.CopyToAsync(ms).ConfigureAwait(false); - ms.Position = 0; - return this.Identify(ms); + return this.Identify(stream); + } + else + { + using (var ms = new MemoryStream()) + { + await stream.CopyToAsync(ms).ConfigureAwait(false); + ms.Position = 0; + return this.Identify(ms); + } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 3b16048f3..c0da8d40b 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -64,11 +64,18 @@ namespace SixLabors.ImageSharp.Formats.Tga public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { - using (var ms = new MemoryStream()) + if (stream.CanSeek) { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); + this.Encode(image, stream); + } + else + { + using (var ms = new MemoryStream()) + { + this.Encode(image, ms); + ms.Position = 0; + await ms.CopyToAsync(stream).ConfigureAwait(false); + } } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index c43a20842..5de580283 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -3,7 +3,7 @@ using System; using System.IO; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; @@ -98,6 +98,21 @@ namespace SixLabors.ImageSharp this.AcceptVisitor(new EncodeVisitor(encoder, stream)); } + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream or encoder is null. + public Task SaveAsync(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + this.EnsureNotDisposed(); + + return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)); + } + /// /// Returns a copy of the image in the given pixel format. /// @@ -140,7 +155,15 @@ namespace SixLabors.ImageSharp /// The visitor. internal abstract void Accept(IImageVisitor visitor); - private class EncodeVisitor : IImageVisitor + /// + /// Accepts a . + /// Implemented by invoking + /// with the pixel type of the image. + /// + /// The visitor. + internal abstract Task AcceptAsync(IImageVisitorAsync visitor); + + private class EncodeVisitor : IImageVisitor, IImageVisitorAsync { private readonly IImageEncoder encoder; @@ -157,6 +180,12 @@ namespace SixLabors.ImageSharp { this.encoder.Encode(image, this.stream); } + + public Task VisitAsync(Image image) + where TPixel : unmanaged, IPixel + { + return this.encoder.EncodeAsync(image, this.stream); + } } } } diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index aa9030c6e..a71cc4064 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -22,40 +23,36 @@ namespace SixLabors.ImageSharp /// The file path to save the image to. /// The path is null. public static void Save(this Image source, string path) - { - Guard.NotNull(path, nameof(path)); + => source.Save(path, source.FindEncoded(path)); - string ext = Path.GetExtension(path); - IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); - if (format is null) - { - var sb = new StringBuilder(); - sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); - foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) - { - sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); - } - - IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The source image. + /// The file path to save the image to. + /// The path is null. + public static Task SaveAsync(this Image source, string path) + => source.SaveAsync(path, source.FindEncoded(path)); - if (encoder is null) + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The source image. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The path is null. + /// The encoder is null. + public static void Save(this Image source, string path, IImageEncoder encoder) + { + Guard.NotNull(path, nameof(path)); + Guard.NotNull(encoder, nameof(encoder)); + using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) { - var sb = new StringBuilder(); - sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); - foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) - { - sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); + source.Save(fs, encoder); } - - source.Save(path, encoder); } + /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -64,13 +61,13 @@ namespace SixLabors.ImageSharp /// The encoder to save the image with. /// The path is null. /// The encoder is null. - public static void Save(this Image source, string path, IImageEncoder encoder) + public static async Task SaveAsync(this Image source, string path, IImageEncoder encoder) { Guard.NotNull(path, nameof(path)); Guard.NotNull(encoder, nameof(encoder)); using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) { - source.Save(fs, encoder); + await source.SaveAsync(fs, encoder).ConfigureAwait(false); } } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 7eda2050a..64aa8ee0b 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -288,6 +289,14 @@ namespace SixLabors.ImageSharp visitor.Visit(this); } + /// + internal override Task AcceptAsync(IImageVisitorAsync visitor) + { + this.EnsureNotDisposed(); + + return visitor.VisitAsync(this); + } + /// /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs new file mode 100644 index 000000000..0f87df7b2 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -0,0 +1,109 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.IO; + +using Moq; + +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; + using SixLabors.ImageSharp.Formats; + using SixLabors.ImageSharp.Tests.TestUtilities; + + public partial class ImageTests + { + public class SaveAsync + { + + [Fact] + public async Task DetectedEncoding() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "DetectedEncodingAsync.png"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task WhenExtensionIsUnknown_Throws() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + + await Assert.ThrowsAsync( + async () => + { + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file); + } + }); + } + + [Fact] + public async Task SetEncoding() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = System.IO.Path.Combine(dir, "SetEncoding.dat"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file, new PngEncoder()); + } + + using (Image.Load(file, out var mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task ThrowsWhenDisposed() + { + var image = new Image(5, 5); + image.Dispose(); + IImageEncoder encoder = Mock.Of(); + using (var stream = new MemoryStream()) + { + await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); + } + } + + [Theory] + [InlineData("test.png")] + [InlineData("test.tga")] + [InlineData("test.bmp")] + [InlineData("test.jpg")] + [InlineData("test.gif")] + public async Task SaveNeverCallsSyncMethods(string filename) + { + using (var image = new Image(5, 5)) + { + IImageEncoder encoder = image.FindEncoded(filename); + using (var stream = new MemoryStream()) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + await image.SaveAsync(asyncStream, encoder); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs new file mode 100644 index 000000000..dc4133fbf --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + // https://github.com/dotnet/aspnetcore/blob/620c673705bb17b33cbc5ff32872d85a5fbf82b9/src/Hosting/TestHost/src/AsyncStreamWrapper.cs + internal class AsyncStreamWrapper : Stream + { + private Stream inner; + private Func allowSynchronousIO; + + internal AsyncStreamWrapper(Stream inner, Func allowSynchronousIO) + { + this.inner = inner; + this.allowSynchronousIO = allowSynchronousIO; + } + + public override bool CanRead => this.inner.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => this.inner.CanWrite; + + public override long Length => throw new NotSupportedException("The stream is not seekable."); + + public override long Position + { + get => throw new NotSupportedException("The stream is not seekable."); + set => throw new NotSupportedException("The stream is not seekable."); + } + + public override void Flush() + { + // Not blocking Flush because things like StreamWriter.Dispose() always call it. + this.inner.Flush(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return this.inner.FlushAsync(cancellationToken); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (!this.allowSynchronousIO()) + { + throw new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true."); + } + + return this.inner.Read(buffer, offset, count); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.inner.ReadAsync(buffer, offset, count, cancellationToken); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.inner.BeginRead(buffer, offset, count, callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return this.inner.EndRead(asyncResult); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("The stream is not seekable."); + } + + public override void SetLength(long value) + { + throw new NotSupportedException("The stream is not seekable."); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (!this.allowSynchronousIO()) + { + throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true."); + } + + this.inner.Write(buffer, offset, count); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.inner.BeginWrite(buffer, offset, count, callback, state); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + this.inner.EndWrite(asyncResult); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.inner.WriteAsync(buffer, offset, count, cancellationToken); + } + + public override void Close() + { + // Don't dispose the inner stream, we don't want to impact the client stream + } + + protected override void Dispose(bool disposing) + { + // Don't dispose the inner stream, we don't want to impact the client stream + } + } +} From 4ecb74f0eda4ccc581ab612dfc8832c86b94f2a1 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 6 May 2020 10:47:44 +0100 Subject: [PATCH 03/22] implement Load Async apis --- src/ImageSharp/Image.Decode.cs | 45 ++- src/ImageSharp/Image.FromStream.cs | 341 ++++++++++++++++++ ...Load_FromStream_UseDefaultConfiguration.cs | 82 ++++- ...yncOnlyStream.cs => AsyncStreamWrapper.cs} | 0 4 files changed, 458 insertions(+), 10 deletions(-) rename tests/ImageSharp.Tests/TestUtilities/{AsyncOnlyStream.cs => AsyncStreamWrapper.cs} (100%) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index cb6f01ce4..c6f9b8224 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -86,7 +87,6 @@ namespace SixLabors.ImageSharp : null; } -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly /// /// Decodes the image stream to the current image. /// @@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp /// /// A new . /// - private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) -#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly + private static FormattedImage Decode(Stream stream, Configuration config) where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -107,10 +106,32 @@ namespace SixLabors.ImageSharp } Image img = decoder.Decode(config, stream); - return (img, format); + return new FormattedImage(img, format); } - private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) + /// + /// Decodes the image stream to the current image. + /// + /// The stream. + /// the configuration. + /// The pixel format. + /// + /// A new . + /// + private static async Task> DecodeAsync(Stream stream, Configuration config) + where TPixel : unmanaged, IPixel + { + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + if (decoder is null) + { + return (null, null); + } + + Image img = await decoder.DecodeAsync(config, stream); + return new FormattedImage(img, format); + } + + private static FormattedImage Decode(Stream stream, Configuration config) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -119,7 +140,19 @@ namespace SixLabors.ImageSharp } Image img = decoder.Decode(config, stream); - return (img, format); + return new FormattedImage(img, format); + } + + private static async Task DecodeAsync(Stream stream, Configuration config) + { + IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + if (decoder is null) + { + return (null, null); + } + + Image img = await decoder.DecodeAsync(config, stream); + return new FormattedImage(img, format); } /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index bcd11845b..fa4b30d65 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -99,6 +100,21 @@ namespace SixLabors.ImageSharp public static Image Load(Stream stream, out IImageFormat format) => Load(Configuration.Default, stream, out format); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Task LoadWithFormatAsync(Stream stream) + => LoadWithFormatAsync(Configuration.Default, stream); + /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -111,6 +127,18 @@ namespace SixLabors.ImageSharp /// The . public static Image Load(Stream stream) => Load(Configuration.Default, stream); + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Task LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream); + /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -126,6 +154,21 @@ namespace SixLabors.ImageSharp public static Image Load(Stream stream, IImageDecoder decoder) => Load(Configuration.Default, stream, decoder); + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The decoder. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Task LoadAsync(Stream stream, IImageDecoder decoder) + => LoadAsync(Configuration.Default, stream, decoder); + /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -146,6 +189,26 @@ namespace SixLabors.ImageSharp return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); } + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The configuration for the decoder. + /// The stream containing image information. + /// The decoder. + /// The configuration is null. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new .> + public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + { + Guard.NotNull(decoder, nameof(decoder)); + return WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync(configuration, s)); + } + /// /// Decode a new instance of the class from the given stream. /// @@ -159,6 +222,23 @@ namespace SixLabors.ImageSharp /// A new .> public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); + /// + /// Decode a new instance of the class from the given stream. + /// + /// The configuration for the decoder. + /// The stream containing image information. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new .> + public static async Task LoadAsync(Configuration configuration, Stream stream) + { + var fmt = await LoadWithFormatAsync(configuration, stream); + return fmt.Image; + } + /// /// Create a new instance of the class from the given stream. /// @@ -173,6 +253,20 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => Load(Configuration.Default, stream); + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new .> + public static Task> LoadAsync(Stream stream) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, stream); + /// /// Create a new instance of the class from the given stream. /// @@ -188,6 +282,22 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => Load(Configuration.Default, stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new .> + public static Task> LoadWithFormatAsync(Stream stream) + where TPixel : unmanaged, IPixel + => LoadWithFormatAsync(Configuration.Default, stream); + /// /// Create a new instance of the class from the given stream. /// @@ -203,6 +313,21 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new .> + public static Task> LoadAsync(Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => WithSeekableStreamAsync(Configuration.Default, stream, s => decoder.DecodeAsync(Configuration.Default, s)); + /// /// Create a new instance of the class from the given stream. /// @@ -220,6 +345,23 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + /// + /// Create a new instance of the class from the given stream. + /// + /// The Configuration. + /// The stream containing image information. + /// The decoder. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new .> + public static Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync(configuration, s)); + /// /// Create a new instance of the class from the given stream. /// @@ -272,6 +414,89 @@ namespace SixLabors.ImageSharp throw new UnknownImageFormatException(sb.ToString()); } + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static async Task LoadWithFormatAsync(Configuration configuration, Stream stream) + { + (Image img, IImageFormat format) data = await WithSeekableStreamAsync(configuration, stream, s => DecodeAsync(s, configuration)); + + if (data.img != null) + { + return data; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static async Task> LoadWithFormatAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + (Image img, IImageFormat format) data = await WithSeekableStreamAsync(configuration, stream, s => DecodeAsync(s, configuration)); + + if (data.img != null) + { + return data; + } + + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static async Task> LoadAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + (Image img, _) = await LoadWithFormatAsync(configuration, stream); + return img; + } + /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -336,5 +561,121 @@ namespace SixLabors.ImageSharp return action(memoryStream); } } + + private static async Task WithSeekableStreamAsync(Configuration configuration, Stream stream, Func> action) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(stream, nameof(stream)); + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + // to make sure we don't trigger anything with aspnetcore then we just need to make sure we are seekable and we make the copy using CopyToAsync + // if the stream is seekable then we arn't using one of the aspnetcore wrapped streams that error on sync api calls and we can use it with out + // having to further wrap + if (stream.CanSeek) + { + if (configuration.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; + } + + return await action(stream); + } + + using (var memoryStream = new MemoryStream()) // should really find a nice way to use a pool for these!! + { + await stream.CopyToAsync(memoryStream); + memoryStream.Position = 0; + + return await action(memoryStream); + } + } + } + + public readonly struct FormattedImage where TPixel : unmanaged, IPixel + { + public FormattedImage(Image image, IImageFormat format) + { + this.Image = image; + this.Format = format; + } + + public readonly Image Image { get; } + + public readonly IImageFormat Format { get; } + + + public static implicit operator (Image image, IImageFormat format)(FormattedImage value) + { + return (value.Image, value.Format); + } + + public static implicit operator FormattedImage((Image image, IImageFormat format) value) + { + return new FormattedImage(value.image, value.format); + } + + public override bool Equals(object obj) + { + return obj is FormattedImage other && + EqualityComparer>.Default.Equals(this.Image, other.Image) && + EqualityComparer.Default.Equals(this.Format, other.Format); + } + + public override int GetHashCode() + { + return HashCode.Combine(this.Image, this.Format); + } + + public void Deconstruct(out Image image, out IImageFormat format) + { + image = this.Image; + format = this.Format; + } + } + + public readonly struct FormattedImage + { + public FormattedImage(Image image, IImageFormat format) + { + this.Image = image; + this.Format = format; + } + + public readonly Image Image { get; } + + public readonly IImageFormat Format { get; } + + + public static implicit operator (Image image, IImageFormat format)(FormattedImage value) + { + return (value.Image, value.Format); + } + + public static implicit operator FormattedImage((Image image, IImageFormat format) value) + { + return new FormattedImage(value.image, value.format); + } + + public override bool Equals(object obj) + { + return obj is FormattedImage other && + EqualityComparer.Default.Equals(this.Image, other.Image) && + EqualityComparer.Default.Equals(this.Format, other.Format); + } + + public override int GetHashCode() + { + return HashCode.Combine(this.Image, this.Format); + } + + public void Deconstruct(out Image image, out IImageFormat format) + { + image = this.Image; + format = this.Format; + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs index ab3a87a31..9a7960f0f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -3,11 +3,11 @@ using System; using System.IO; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -18,7 +18,17 @@ namespace SixLabors.ImageSharp.Tests { private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - private MemoryStream Stream { get; } = new MemoryStream(Data); + private MemoryStream BaseStream { get; } + + private AsyncStreamWrapper Stream { get; } + + private bool AllowSynchronousIO { get; set; } = true; + + public Load_FromStream_UseDefaultConfiguration() + { + this.BaseStream = new MemoryStream(Data); + this.Stream = new AsyncStreamWrapper(this.BaseStream, () => this.AllowSynchronousIO); + } private static void VerifyDecodedImage(Image img) { @@ -81,9 +91,73 @@ namespace SixLabors.ImageSharp.Tests } } + [Fact] + public async Task Async_Stream_OutFormat_Agnostic() + { + this.AllowSynchronousIO = false; + var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) + { + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); + } + } + + [Fact] + public async Task Async_Stream_Specific() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_Agnostic() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream)) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_OutFormat_Specific() + { + this.AllowSynchronousIO = false; + var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) + { + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); + } + } + + [Fact] + public async Task Async_Stream_Decoder_Specific() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + + [Fact] + public async Task Async_Stream_Decoder_Agnostic() + { + this.AllowSynchronousIO = false; + using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + public void Dispose() { - this.Stream?.Dispose(); + this.BaseStream?.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs similarity index 100% rename from tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs rename to tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs From 8a4363acb31be392a6100546a28c9488f1c7a836 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 6 May 2020 12:09:27 +0100 Subject: [PATCH 04/22] IdentifyAsync + stylecop --- .../Advanced/AdvancedImageExtensions.cs | 1 + src/ImageSharp/Advanced/IImageVisitor.cs | 1 + src/ImageSharp/Formats/Gif/GifDecoder.cs | 1 - src/ImageSharp/Formats/IImageEncoder.cs | 1 + src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 1 + src/ImageSharp/Formats/Png/PngEncoder.cs | 1 + src/ImageSharp/FormattedImage.cs | 79 +++++++ src/ImageSharp/FormattedImageInfo.cs | 79 +++++++ src/ImageSharp/FormattedImage{TPixel}.cs | 82 ++++++++ src/ImageSharp/Image.Decode.cs | 38 +++- src/ImageSharp/Image.FromStream.cs | 195 +++++++++--------- src/ImageSharp/Image.cs | 1 + src/ImageSharp/ImageExtensions.cs | 3 +- .../Image/ImageTests.DetectFormat.cs | 27 ++- .../Image/ImageTests.Identify.cs | 66 +++++- .../Image/ImageTests.ImageLoadTestBase.cs | 1 + .../Image/ImageTests.SaveAsync.cs | 1 - .../TestUtilities/AsyncStreamWrapper.cs | 3 + 18 files changed, 481 insertions(+), 100 deletions(-) create mode 100644 src/ImageSharp/FormattedImage.cs create mode 100644 src/ImageSharp/FormattedImageInfo.cs create mode 100644 src/ImageSharp/FormattedImage{TPixel}.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index c845cfc4f..e3a5938e2 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -75,6 +75,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// The source. /// The visitor. + /// A representing the asynchronous operation. public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) => source.AcceptAsync(visitor); diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index fa7b8e2f1..5c736c43f 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// The image. /// The pixel type. + /// A representing the asynchronous operation. Task VisitAsync(Image image) where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index e5de1c028..4adfcb225 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -76,7 +76,6 @@ namespace SixLabors.ImageSharp.Formats.Gif return decoder.Identify(stream); } - /// public async Task IdentifyAsync(Configuration configuration, Stream stream) { diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index f4d9b2793..8588385f8 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -27,6 +27,7 @@ namespace SixLabors.ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. + /// A representing the asynchronous operation. Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 1838b8d6d..488d7d5f0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -43,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The to encode from. /// The to encode the image data to. + /// A representing the asynchronous operation. public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index a2c2ca100..4209bef61 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The to encode from. /// The to encode the image data to. + /// A representing the asynchronous operation. public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { diff --git a/src/ImageSharp/FormattedImage.cs b/src/ImageSharp/FormattedImage.cs new file mode 100644 index 000000000..5617be351 --- /dev/null +++ b/src/ImageSharp/FormattedImage.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp +{ + /// + /// Struct to curry and for return from async overloads. + /// + public readonly struct FormattedImage + { + /// + /// Initializes a new instance of the struct. + /// + /// The . + /// The . + public FormattedImage(Image image, IImageFormat format) + { + this.Image = image; + this.Format = format; + } + + /// + /// Gets the Image. + /// + public readonly Image Image { get; } + + /// + /// Gets the Format. + /// + public readonly IImageFormat Format { get; } + + /// + /// Converts to + /// + /// The to convert. + public static implicit operator (Image image, IImageFormat format)(FormattedImage value) + { + return (value.Image, value.Format); + } + + /// + /// Converts to + /// + /// The to convert. + public static implicit operator FormattedImage((Image image, IImageFormat format) value) + { + return new FormattedImage(value.image, value.format); + } + + /// + public override bool Equals(object obj) + { + return obj is FormattedImage other && + EqualityComparer.Default.Equals(this.Image, other.Image) && + EqualityComparer.Default.Equals(this.Format, other.Format); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Image, this.Format); + } + + /// + /// Deconstructs into component parts. + /// + /// The . + /// The . + public void Deconstruct(out Image image, out IImageFormat format) + { + image = this.Image; + format = this.Format; + } + } +} diff --git a/src/ImageSharp/FormattedImageInfo.cs b/src/ImageSharp/FormattedImageInfo.cs new file mode 100644 index 000000000..b3f854fc4 --- /dev/null +++ b/src/ImageSharp/FormattedImageInfo.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp +{ + /// + /// Struct to curry and for return from async overloads. + /// + public readonly struct FormattedImageInfo + { + /// + /// Initializes a new instance of the struct. + /// + /// The . + /// The . + public FormattedImageInfo(IImageInfo imageInfo, IImageFormat format) + { + this.ImageInfo = imageInfo; + this.Format = format; + } + + /// + /// Gets the Image Info. + /// + public readonly IImageInfo ImageInfo { get; } + + /// + /// Gets the Format. + /// + public readonly IImageFormat Format { get; } + + /// + /// Converts to a + /// + /// The to convert. + public static implicit operator (IImageInfo imageInfo, IImageFormat format)(FormattedImageInfo value) + { + return (value.ImageInfo, value.Format); + } + + /// + /// Converts to + /// + /// The to convert. + public static implicit operator FormattedImageInfo((IImageInfo imageInfo, IImageFormat format) value) + { + return new FormattedImageInfo(value.imageInfo, value.format); + } + + /// + public override bool Equals(object obj) + { + return obj is FormattedImageInfo other && + EqualityComparer.Default.Equals(this.ImageInfo, other.ImageInfo) && + EqualityComparer.Default.Equals(this.Format, other.Format); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.ImageInfo, this.Format); + } + + /// + /// Deconstructs into component parts. + /// + /// The . + /// The . + public void Deconstruct(out IImageInfo imageInfo, out IImageFormat format) + { + imageInfo = this.ImageInfo; + format = this.Format; + } + } +} diff --git a/src/ImageSharp/FormattedImage{TPixel}.cs b/src/ImageSharp/FormattedImage{TPixel}.cs new file mode 100644 index 000000000..dc4609b7e --- /dev/null +++ b/src/ImageSharp/FormattedImage{TPixel}.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + +using System; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// Struct to curry and for return from async overloads. + /// + /// The pixel format. + public readonly struct FormattedImage + where TPixel : unmanaged, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// The . + /// The . + public FormattedImage(Image image, IImageFormat format) + { + this.Image = image; + this.Format = format; + } + + /// + /// Gets the Image. + /// + public readonly Image Image { get; } + + /// + /// Gets the Format. + /// + public readonly IImageFormat Format { get; } + + /// + /// Converts to . + /// + /// The to convert. + public static implicit operator (Image image, IImageFormat format)(FormattedImage value) + { + return (value.Image, value.Format); + } + + /// + /// Converts to + /// + /// The to convert. + public static implicit operator FormattedImage((Image image, IImageFormat format) value) + { + return new FormattedImage(value.image, value.format); + } + + /// + public override bool Equals(object obj) + { + return obj is FormattedImage other && + EqualityComparer>.Default.Equals(this.Image, other.Image) && + EqualityComparer.Default.Equals(this.Format, other.Format); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Image, this.Format); + } + + /// + /// Deconstructs into component parts. + /// + /// The . + /// The . + public void Deconstruct(out Image image, out IImageFormat format) + { + image = this.Image; + format = this.Format; + } + } +} diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index c6f9b8224..c1cf5cc14 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -71,6 +71,20 @@ namespace SixLabors.ImageSharp } } + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The mime type or null if none found. + private static Task InternalDetectFormatAsync(Stream stream, Configuration config) + { + // we are going to cheat here because we know that by this point we have been wrapped in a + // seekable stream then we are free to use sync APIs this is potentially brittle and may + // need a better fix in the future. + return Task.FromResult(InternalDetectFormat(stream, config)); + } + /// /// By reading the header on the provided stream this calculates the images format. /// @@ -163,14 +177,34 @@ namespace SixLabors.ImageSharp /// /// The or null if suitable info detector not found. /// - private static (IImageInfo info, IImageFormat format) InternalIdentity(Stream stream, Configuration config) + private static FormattedImageInfo InternalIdentity(Stream stream, Configuration config) + { + if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) + { + return (null, null); + } + + var info = detector?.Identify(config, stream); + return new FormattedImageInfo(info, format); + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The stream. + /// the configuration. + /// + /// The or null if suitable info detector not found. + /// + private static async Task InternalIdentityAsync(Stream stream, Configuration config) { if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) { return (null, null); } - return (detector?.Identify(config, stream), format); + var info = await detector?.IdentifyAsync(config, stream); + return new FormattedImageInfo(info, format); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index fa4b30d65..713270525 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -38,6 +38,28 @@ namespace SixLabors.ImageSharp public static IImageFormat DetectFormat(Configuration configuration, Stream stream) => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static Task DetectFormatAsync(Stream stream) + => DetectFormatAsync(Configuration.Default, stream); + + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The configuration. + /// The image stream to read the header from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static Task DetectFormatAsync(Configuration configuration, Stream stream) + => WithSeekableStreamAsync(configuration, stream, s => InternalDetectFormatAsync(s, configuration)); + /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -51,6 +73,19 @@ namespace SixLabors.ImageSharp public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _); + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if suitable info detector not found. + /// + public static Task IdentifyAsync(Stream stream) + => IdentifyAsync(Configuration.Default, stream); + /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -65,6 +100,39 @@ namespace SixLabors.ImageSharp public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format); + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, Stream stream) + => Identify(configuration, stream, out _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if suitable info detector is not found. + /// + public static async Task IdentifyAsync(Configuration configuration, Stream stream) + { + FormattedImageInfo res = await IdentifyWithFormatAsync(configuration, stream); + return res.ImageInfo; + } + /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -80,12 +148,41 @@ namespace SixLabors.ImageSharp /// public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) { - (IImageInfo info, IImageFormat format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); + FormattedImageInfo data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); - format = data.format; - return data.info; + format = data.Format; + return data.ImageInfo; } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the information from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The with set to null if suitable info detector is not found. + /// + public static Task IdentifyWithFormatAsync(Stream stream) + => IdentifyWithFormatAsync(Configuration.Default, stream); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The with set to null if suitable info detector is not found. + /// + public static Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) + => WithSeekableStreamAsync(configuration, stream, s => InternalIdentityAsync(s, configuration ?? Configuration.Default)); + /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -100,18 +197,16 @@ namespace SixLabors.ImageSharp public static Image Load(Stream stream, out IImageFormat format) => Load(Configuration.Default, stream, out format); - /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// /// The stream containing image information. - /// The format type of the decoded image. /// The stream is null. /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// The . + /// A representing the asynchronous operation. public static Task LoadWithFormatAsync(Stream stream) => LoadWithFormatAsync(Configuration.Default, stream); @@ -282,18 +377,16 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => Load(Configuration.Default, stream, out format); - /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// The format type of the decoded image. /// The stream is null. /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A representing the asynchronous operation. public static Task> LoadWithFormatAsync(Stream stream) where TPixel : unmanaged, IPixel => LoadWithFormatAsync(Configuration.Default, stream); @@ -594,88 +687,4 @@ namespace SixLabors.ImageSharp } } } - - public readonly struct FormattedImage where TPixel : unmanaged, IPixel - { - public FormattedImage(Image image, IImageFormat format) - { - this.Image = image; - this.Format = format; - } - - public readonly Image Image { get; } - - public readonly IImageFormat Format { get; } - - - public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - { - return (value.Image, value.Format); - } - - public static implicit operator FormattedImage((Image image, IImageFormat format) value) - { - return new FormattedImage(value.image, value.format); - } - - public override bool Equals(object obj) - { - return obj is FormattedImage other && - EqualityComparer>.Default.Equals(this.Image, other.Image) && - EqualityComparer.Default.Equals(this.Format, other.Format); - } - - public override int GetHashCode() - { - return HashCode.Combine(this.Image, this.Format); - } - - public void Deconstruct(out Image image, out IImageFormat format) - { - image = this.Image; - format = this.Format; - } - } - - public readonly struct FormattedImage - { - public FormattedImage(Image image, IImageFormat format) - { - this.Image = image; - this.Format = format; - } - - public readonly Image Image { get; } - - public readonly IImageFormat Format { get; } - - - public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - { - return (value.Image, value.Format); - } - - public static implicit operator FormattedImage((Image image, IImageFormat format) value) - { - return new FormattedImage(value.image, value.format); - } - - public override bool Equals(object obj) - { - return obj is FormattedImage other && - EqualityComparer.Default.Equals(this.Image, other.Image) && - EqualityComparer.Default.Equals(this.Format, other.Format); - } - - public override int GetHashCode() - { - return HashCode.Combine(this.Image, this.Format); - } - - public void Deconstruct(out Image image, out IImageFormat format) - { - image = this.Image; - format = this.Format; - } - } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 5de580283..8a691a4b3 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -104,6 +104,7 @@ namespace SixLabors.ImageSharp /// The stream to save the image to. /// The encoder to save the image with. /// Thrown if the stream or encoder is null. + /// A representing the asynchronous operation. public Task SaveAsync(Stream stream, IImageEncoder encoder) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index a71cc4064..df2c12106 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp /// The source image. /// The file path to save the image to. /// The path is null. + /// A representing the asynchronous operation. public static Task SaveAsync(this Image source, string path) => source.SaveAsync(path, source.FindEncoded(path)); @@ -52,7 +53,6 @@ namespace SixLabors.ImageSharp } } - /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -61,6 +61,7 @@ namespace SixLabors.ImageSharp /// The encoder to save the image with. /// The path is null. /// The encoder is null. + /// A representing the asynchronous operation. public static async Task SaveAsync(this Image source, string path, IImageEncoder encoder) { Guard.NotNull(path, nameof(path)); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index f923832ab..b1acea967 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -3,8 +3,9 @@ using System; using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; // ReSharper disable InconsistentNaming @@ -91,6 +92,30 @@ namespace SixLabors.ImageSharp.Tests IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); Assert.Null(type); } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); + Assert.Equal(ExpectedGlobalFormat, type); + } + } + + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + IImageFormat type = await Image.DetectFormatAsync(this.LocalConfiguration, new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + IImageFormat type = await Image.DetectFormatAsync(new Configuration(), new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Null(type); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index c7dbbc2d8..8493db073 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -2,8 +2,9 @@ // Licensed under the GNU Affero General Public License, Version 3. using System.IO; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; // ReSharper disable InconsistentNaming @@ -77,6 +78,17 @@ namespace SixLabors.ImageSharp.Tests } } + [Fact] + public void FromStream_GlobalConfiguration_NoFormat() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageInfo info = Image.Identify(stream); + + Assert.NotNull(info); + } + } + [Fact] public void FromStream_CustomConfiguration() { @@ -86,6 +98,14 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(this.LocalImageFormat, type); } + [Fact] + public void FromStream_CustomConfiguration_NoFormat() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream); + + Assert.Equal(this.LocalImageInfo, info); + } + [Fact] public void WhenNoMatchingFormatFound_ReturnsNull() { @@ -94,6 +114,50 @@ namespace SixLabors.ImageSharp.Tests Assert.Null(info); Assert.Null(type); } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration_NoFormat() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + IImageInfo info = await Image.IdentifyAsync(asyncStream); + + Assert.NotNull(info); + } + } + + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + FormattedImageInfo info = await Image.IdentifyWithFormatAsync(asyncStream); + + Assert.NotNull(info.ImageInfo); + Assert.Equal(ExpectedGlobalFormat, info.Format); + } + } + + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + FormattedImageInfo info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); + + Assert.Equal(this.LocalImageInfo, info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } + + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + FormattedImageInfo info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); + + Assert.Null(info.ImageInfo); + } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 5941854c1..6085fff2e 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 0f87df7b2..a327861ca 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -23,7 +23,6 @@ namespace SixLabors.ImageSharp.Tests { public class SaveAsync { - [Fact] public async Task DetectedEncoding() { diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs index dc4133fbf..3d23a3c3b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the GNU Affero General Public License, Version 3. + using System; using System.Collections.Generic; using System.IO; From d8fe0f52dbcacf07483cd7dd929d0a20a4ca9def Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:19:14 +0100 Subject: [PATCH 05/22] Update src/ImageSharp/Formats/Jpeg/JpegDecoder.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 09bf77022..810308744 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg using var decoder = new JpegDecoderCore(configuration, this); try { - return await decoder.DecodeAsync(stream); + return await decoder.DecodeAsync(stream).ConfigureAwait(false); } catch (InvalidMemoryOperationException ex) { From 9b85a353158bd2534c760dfb5ba63eaf1ac505fb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:19:27 +0100 Subject: [PATCH 06/22] Update src/ImageSharp/Formats/Png/PngEncoder.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Formats/Png/PngEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 4209bef61..2c85f1407 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Png { using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { - await encoder.EncodeAsync(image, stream); + await encoder.EncodeAsync(image, stream).ConfigureAwait(false); } } } From d1a14e50e406d09d2eab4013f867f5c1fd7acf22 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:19:43 +0100 Subject: [PATCH 07/22] Update src/ImageSharp/Image.Decode.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.Decode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index c1cf5cc14..7afd1ab53 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = await decoder.DecodeAsync(config, stream); + Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); return new FormattedImage(img, format); } From 0893db41c67e7ab58562f05606cdd2278c7555b3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:19:56 +0100 Subject: [PATCH 08/22] Update src/ImageSharp/Image.Decode.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.Decode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 7afd1ab53..1adcc4a79 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = await decoder.DecodeAsync(config, stream); + Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); return new FormattedImage(img, format); } From 95c76697351d0ecd691c39a3f2e37d5290dd9199 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:20:14 +0100 Subject: [PATCH 09/22] Update src/ImageSharp/Image.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 8a691a4b3..fd2fb1f87 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp public Task VisitAsync(Image image) where TPixel : unmanaged, IPixel { - return this.encoder.EncodeAsync(image, this.stream); + return this.encoder.EncodeAsync(image, this.stream).ConfigureAwait(false); } } } From e7899ed7890439852a76dad84962efae0d7ea567 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:20:26 +0100 Subject: [PATCH 10/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 713270525..5be263bf6 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp /// A new .> public static async Task LoadAsync(Configuration configuration, Stream stream) { - var fmt = await LoadWithFormatAsync(configuration, stream); + var fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); return fmt.Image; } From 4ada7df910311a36d0bfed48f9c7e470abfcbba8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:20:39 +0100 Subject: [PATCH 11/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 5be263bf6..bb6b5b1e8 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp /// public static async Task IdentifyAsync(Configuration configuration, Stream stream) { - FormattedImageInfo res = await IdentifyWithFormatAsync(configuration, stream); + FormattedImageInfo res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); return res.ImageInfo; } From beeb4d7dfb3269e1712d1ac3e1a3623f65be91a9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:20:51 +0100 Subject: [PATCH 12/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index bb6b5b1e8..f5a181da3 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp public static async Task> LoadAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { - (Image img, _) = await LoadWithFormatAsync(configuration, stream); + (Image img, _) = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); return img; } From 393e95e90fb1b7490de8fe784bc786c5f84b0db0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:21:03 +0100 Subject: [PATCH 13/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index f5a181da3..504281011 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -675,7 +675,7 @@ namespace SixLabors.ImageSharp stream.Position = 0; } - return await action(stream); + return await action(stream).ConfigureAwait(false); } using (var memoryStream = new MemoryStream()) // should really find a nice way to use a pool for these!! From 5a7720a30a2b63ccddf0e81787f1b604391b6748 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:21:13 +0100 Subject: [PATCH 14/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 504281011..cb6ac226b 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -683,7 +683,7 @@ namespace SixLabors.ImageSharp await stream.CopyToAsync(memoryStream); memoryStream.Position = 0; - return await action(memoryStream); + return await action(memoryStream).ConfigureAwait(false); } } } From ad0e9e12077871a826477c6bef4f9ef81a6a41df Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 11:21:31 +0100 Subject: [PATCH 15/22] Update src/ImageSharp/Image.FromStream.cs Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com> --- src/ImageSharp/Image.FromStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index cb6ac226b..cdd44a4fd 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -680,7 +680,7 @@ namespace SixLabors.ImageSharp using (var memoryStream = new MemoryStream()) // should really find a nice way to use a pool for these!! { - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; return await action(memoryStream).ConfigureAwait(false); From dd9aa7908ec2f8ea5ae8285b4c5c41264654c36a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 15:11:53 +0100 Subject: [PATCH 16/22] async await where required --- .../Advanced/AdvancedImageExtensions.cs | 6 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 4 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 4 +- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 4 +- src/ImageSharp/Formats/Tga/TgaEncoder.cs | 4 +- src/ImageSharp/Image.Decode.cs | 11 ++- src/ImageSharp/Image.FromStream.cs | 67 ++++++++++++++----- src/ImageSharp/Image.cs | 15 ++--- 8 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index e3a5938e2..185ac9cc4 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; @@ -76,8 +74,8 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The visitor. /// A representing the asynchronous operation. - public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) - => source.AcceptAsync(visitor); + public static async Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) + => await source.AcceptAsync(visitor).ConfigureAwait(false); /// /// Gets the configuration for the image. diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 52b87ef27..70023ffc1 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -42,11 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public Task EncodeAsync(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream); + await encoder.EncodeAsync(image, stream).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 8d4c33eff..78e78a81f 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public Task EncodeAsync(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new GifEncoderCore(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream); + await encoder.EncodeAsync(image, stream).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 5cd7ca7b0..f977623f4 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -74,11 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public async Task IdentifyAsync(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).IdentifyAsync(stream); + return await new TgaDecoderCore(configuration, this).IdentifyAsync(stream).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index 058dd3559..449a724bf 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public Task EncodeAsync(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream); + await encoder.EncodeAsync(image, stream).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 1adcc4a79..9623e6e43 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp /// The mime type or null if none found. private static Task InternalDetectFormatAsync(Stream stream, Configuration config) { - // we are going to cheat here because we know that by this point we have been wrapped in a + // We are going to cheat here because we know that by this point we have been wrapped in a // seekable stream then we are free to use sync APIs this is potentially brittle and may // need a better fix in the future. return Task.FromResult(InternalDetectFormat(stream, config)); @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp return (null, null); } - var info = detector?.Identify(config, stream); + IImageInfo info = detector?.Identify(config, stream); return new FormattedImageInfo(info, format); } @@ -203,7 +203,12 @@ namespace SixLabors.ImageSharp return (null, null); } - var info = await detector?.IdentifyAsync(config, stream); + if (detector is null) + { + return (null, format); + } + + IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false); return new FormattedImageInfo(info, format); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index cdd44a4fd..bb36b1462 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -57,8 +57,12 @@ namespace SixLabors.ImageSharp /// The stream is null. /// The stream is not readable. /// The format type or null if none found. - public static Task DetectFormatAsync(Configuration configuration, Stream stream) - => WithSeekableStreamAsync(configuration, stream, s => InternalDetectFormatAsync(s, configuration)); + public static async Task DetectFormatAsync(Configuration configuration, Stream stream) + => await WithSeekableStreamAsync( + configuration, + stream, + async s => await InternalDetectFormatAsync(s, configuration).ConfigureAwait(false)) + .ConfigureAwait(false); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -180,8 +184,12 @@ namespace SixLabors.ImageSharp /// /// The with set to null if suitable info detector is not found. /// - public static Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) - => WithSeekableStreamAsync(configuration, stream, s => InternalIdentityAsync(s, configuration ?? Configuration.Default)); + public static async Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) + => await WithSeekableStreamAsync( + configuration, + stream, + async s => await InternalIdentityAsync(s, configuration ?? Configuration.Default)) + .ConfigureAwait(false); /// /// Decode a new instance of the class from the given stream. @@ -330,7 +338,7 @@ namespace SixLabors.ImageSharp /// A new .> public static async Task LoadAsync(Configuration configuration, Stream stream) { - var fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); + FormattedImage fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); return fmt.Image; } @@ -387,9 +395,9 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadWithFormatAsync(Stream stream) + public static async Task> LoadWithFormatAsync(Stream stream) where TPixel : unmanaged, IPixel - => LoadWithFormatAsync(Configuration.Default, stream); + => await LoadWithFormatAsync(Configuration.Default, stream).ConfigureAwait(false); /// /// Create a new instance of the class from the given stream. @@ -417,9 +425,13 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new .> - public static Task> LoadAsync(Stream stream, IImageDecoder decoder) + public static async Task> LoadAsync(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync(Configuration.Default, stream, s => decoder.DecodeAsync(Configuration.Default, s)); + => await WithSeekableStreamAsync( + Configuration.Default, + stream, + async s => await decoder.DecodeAsync(Configuration.Default, s).ConfigureAwait(false)) + .ConfigureAwait(false); /// /// Create a new instance of the class from the given stream. @@ -451,9 +463,13 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new .> - public static Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + public static async Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync(configuration, s)); + => await WithSeekableStreamAsync( + configuration, + stream, + async s => await decoder.DecodeAsync(configuration, s).ConfigureAwait(false)) + .ConfigureAwait(false); /// /// Create a new instance of the class from the given stream. @@ -520,7 +536,11 @@ namespace SixLabors.ImageSharp /// A new . public static async Task LoadWithFormatAsync(Configuration configuration, Stream stream) { - (Image img, IImageFormat format) data = await WithSeekableStreamAsync(configuration, stream, s => DecodeAsync(s, configuration)); + (Image img, IImageFormat format) data = await WithSeekableStreamAsync( + configuration, + stream, + async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) + .ConfigureAwait(false); if (data.img != null) { @@ -553,7 +573,12 @@ namespace SixLabors.ImageSharp public static async Task> LoadWithFormatAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { - (Image img, IImageFormat format) data = await WithSeekableStreamAsync(configuration, stream, s => DecodeAsync(s, configuration)); + (Image img, IImageFormat format) data = + await WithSeekableStreamAsync( + configuration, + stream, + async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) + .ConfigureAwait(false); if (data.img != null) { @@ -646,6 +671,9 @@ 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()) { stream.CopyTo(memoryStream); @@ -655,7 +683,10 @@ namespace SixLabors.ImageSharp } } - private static async Task WithSeekableStreamAsync(Configuration configuration, Stream stream, Func> action) + private static async Task WithSeekableStreamAsync( + Configuration configuration, + Stream stream, + Func> action) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(stream, nameof(stream)); @@ -665,8 +696,9 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } - // to make sure we don't trigger anything with aspnetcore then we just need to make sure we are seekable and we make the copy using CopyToAsync - // if the stream is seekable then we arn't using one of the aspnetcore wrapped streams that error on sync api calls and we can use it with out + // To make sure we don't trigger anything with aspnetcore then we just need to make sure we are + // seekable and we make the copy using CopyToAsync if the stream is seekable then we arn't using + // one of the aspnetcore wrapped streams that error on sync api calls and we can use it without // having to further wrap if (stream.CanSeek) { @@ -678,7 +710,8 @@ namespace SixLabors.ImageSharp return await action(stream).ConfigureAwait(false); } - using (var memoryStream = new MemoryStream()) // should really find a nice way to use a pool for these!! + // TODO: See above comment. + using (var memoryStream = new MemoryStream()) { await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index fd2fb1f87..167847360 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp /// The encoder to save the image with. /// Thrown if the stream or encoder is null. /// A representing the asynchronous operation. - public Task SaveAsync(Stream stream, IImageEncoder encoder) + public async Task SaveAsync(Stream stream, IImageEncoder encoder) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); this.EnsureNotDisposed(); - return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)); + await this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)).ConfigureAwait(false); } /// @@ -177,16 +177,11 @@ namespace SixLabors.ImageSharp } public void Visit(Image image) - where TPixel : unmanaged, IPixel - { - this.encoder.Encode(image, this.stream); - } + where TPixel : unmanaged, IPixel => this.encoder.Encode(image, this.stream); - public Task VisitAsync(Image image) + public async Task VisitAsync(Image image) where TPixel : unmanaged, IPixel - { - return this.encoder.EncodeAsync(image, this.stream).ConfigureAwait(false); - } + => await this.encoder.EncodeAsync(image, this.stream).ConfigureAwait(false); } } } From 45ad33f2ae6421d9318f91e9316f2cff643f3fb7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 15:12:16 +0100 Subject: [PATCH 17/22] implement IEquatable --- src/ImageSharp/FormattedImage.cs | 56 +++++++++++++++--------- src/ImageSharp/FormattedImageInfo.cs | 46 ++++++++++++------- src/ImageSharp/FormattedImage{TPixel}.cs | 54 +++++++++++++++-------- 3 files changed, 101 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/FormattedImage.cs b/src/ImageSharp/FormattedImage.cs index 5617be351..9b604eced 100644 --- a/src/ImageSharp/FormattedImage.cs +++ b/src/ImageSharp/FormattedImage.cs @@ -8,14 +8,14 @@ using SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp { /// - /// Struct to curry and for return from async overloads. + /// Struct to curry and for return from async overloads. /// - public readonly struct FormattedImage + public readonly struct FormattedImage : IEquatable { /// /// Initializes a new instance of the struct. /// - /// The . + /// The . /// The . public FormattedImage(Image image, IImageFormat format) { @@ -34,41 +34,57 @@ namespace SixLabors.ImageSharp public readonly IImageFormat Format { get; } /// - /// Converts to + /// Converts to . /// /// The to convert. public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - { - return (value.Image, value.Format); - } + => (value.Image, value.Format); /// /// Converts to /// /// The to convert. public static implicit operator FormattedImage((Image image, IImageFormat format) value) - { - return new FormattedImage(value.image, value.format); - } + => new FormattedImage(value.image, value.format); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(FormattedImage left, FormattedImage right) + => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(FormattedImage left, FormattedImage right) + => !(left == right); /// public override bool Equals(object obj) - { - return obj is FormattedImage other && - EqualityComparer.Default.Equals(this.Image, other.Image) && - EqualityComparer.Default.Equals(this.Format, other.Format); - } + => obj is FormattedImage image && this.Equals(image); /// - public override int GetHashCode() - { - return HashCode.Combine(this.Image, this.Format); - } + public bool Equals(FormattedImage other) + => EqualityComparer.Default.Equals(this.Image, other.Image) + && EqualityComparer.Default.Equals(this.Format, other.Format); + + /// + public override int GetHashCode() => HashCode.Combine(this.Image, this.Format); /// /// Deconstructs into component parts. /// - /// The . + /// The . /// The . public void Deconstruct(out Image image, out IImageFormat format) { diff --git a/src/ImageSharp/FormattedImageInfo.cs b/src/ImageSharp/FormattedImageInfo.cs index b3f854fc4..72368be34 100644 --- a/src/ImageSharp/FormattedImageInfo.cs +++ b/src/ImageSharp/FormattedImageInfo.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp /// /// Struct to curry and for return from async overloads. /// - public readonly struct FormattedImageInfo + public readonly struct FormattedImageInfo : IEquatable { /// /// Initializes a new instance of the struct. @@ -38,32 +38,46 @@ namespace SixLabors.ImageSharp /// /// The to convert. public static implicit operator (IImageInfo imageInfo, IImageFormat format)(FormattedImageInfo value) - { - return (value.ImageInfo, value.Format); - } + => (value.ImageInfo, value.Format); /// /// Converts to /// /// The to convert. public static implicit operator FormattedImageInfo((IImageInfo imageInfo, IImageFormat format) value) - { - return new FormattedImageInfo(value.imageInfo, value.format); - } + => new FormattedImageInfo(value.imageInfo, value.format); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(FormattedImageInfo left, FormattedImageInfo right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(FormattedImageInfo left, FormattedImageInfo right) => !(left == right); /// public override bool Equals(object obj) - { - return obj is FormattedImageInfo other && - EqualityComparer.Default.Equals(this.ImageInfo, other.ImageInfo) && - EqualityComparer.Default.Equals(this.Format, other.Format); - } + => obj is FormattedImageInfo info && this.Equals(info); /// - public override int GetHashCode() - { - return HashCode.Combine(this.ImageInfo, this.Format); - } + public bool Equals(FormattedImageInfo other) + => EqualityComparer.Default.Equals(this.ImageInfo, other.ImageInfo) + && EqualityComparer.Default.Equals(this.Format, other.Format); + + /// + public override int GetHashCode() => HashCode.Combine(this.ImageInfo, this.Format); /// /// Deconstructs into component parts. diff --git a/src/ImageSharp/FormattedImage{TPixel}.cs b/src/ImageSharp/FormattedImage{TPixel}.cs index dc4609b7e..bb3eeabe7 100644 --- a/src/ImageSharp/FormattedImage{TPixel}.cs +++ b/src/ImageSharp/FormattedImage{TPixel}.cs @@ -9,16 +9,16 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Struct to curry and for return from async overloads. + /// Struct to curry and for return from async overloads. /// /// The pixel format. - public readonly struct FormattedImage + public readonly struct FormattedImage : IEquatable> where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the struct. /// - /// The . + /// The . /// The . public FormattedImage(Image image, IImageFormat format) { @@ -41,37 +41,53 @@ namespace SixLabors.ImageSharp /// /// The to convert. public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - { - return (value.Image, value.Format); - } + => (value.Image, value.Format); /// /// Converts to /// /// The to convert. public static implicit operator FormattedImage((Image image, IImageFormat format) value) - { - return new FormattedImage(value.image, value.format); - } + => new FormattedImage(value.image, value.format); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(FormattedImage left, FormattedImage right) + => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(FormattedImage left, FormattedImage right) + => !(left == right); /// public override bool Equals(object obj) - { - return obj is FormattedImage other && - EqualityComparer>.Default.Equals(this.Image, other.Image) && - EqualityComparer.Default.Equals(this.Format, other.Format); - } + => obj is FormattedImage image && this.Equals(image); /// - public override int GetHashCode() - { - return HashCode.Combine(this.Image, this.Format); - } + public bool Equals(FormattedImage other) + => EqualityComparer>.Default.Equals(this.Image, other.Image) + && EqualityComparer.Default.Equals(this.Format, other.Format); + + /// + public override int GetHashCode() => HashCode.Combine(this.Image, this.Format); /// /// Deconstructs into component parts. /// - /// The . + /// The . /// The . public void Deconstruct(out Image image, out IImageFormat format) { From 29eb7efddef0ada02283b2ad1710ab080bd717d1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 15:38:52 +0100 Subject: [PATCH 18/22] Rename extension --- .../Advanced/AdvancedImageExtensions.cs | 20 +++++++++---------- src/ImageSharp/ImageExtensions.cs | 4 ++-- .../Image/ImageTests.SaveAsync.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 185ac9cc4..61dfc8944 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -18,16 +18,16 @@ namespace SixLabors.ImageSharp.Advanced public static class AdvancedImageExtensions { /// - /// For a given path find the best encoder to use + /// For a given file path find the best encoder to use via its extension. /// - /// The source. - /// The Path + /// The source image. + /// The target file path to save the image to. /// The matching encoder. - public static IImageEncoder FindEncoded(this Image source, string path) + public static IImageEncoder DetectEncoder(this Image source, string filePath) { - Guard.NotNull(path, nameof(path)); + Guard.NotNull(filePath, nameof(filePath)); - string ext = Path.GetExtension(path); + string ext = Path.GetExtension(filePath); IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); if (format is null) { @@ -62,8 +62,8 @@ namespace SixLabors.ImageSharp.Advanced /// Accepts a to implement a double-dispatch pattern in order to /// apply pixel-specific operations on non-generic instances /// - /// The source. - /// The visitor. + /// The source image. + /// The image visitor. public static void AcceptVisitor(this Image source, IImageVisitor visitor) => source.Accept(visitor); @@ -71,8 +71,8 @@ namespace SixLabors.ImageSharp.Advanced /// Accepts a to implement a double-dispatch pattern in order to /// apply pixel-specific operations on non-generic instances /// - /// The source. - /// The visitor. + /// The source image. + /// The image visitor. /// A representing the asynchronous operation. public static async Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) => await source.AcceptAsync(visitor).ConfigureAwait(false); diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index df2c12106..a04688e5a 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The file path to save the image to. /// The path is null. public static void Save(this Image source, string path) - => source.Save(path, source.FindEncoded(path)); + => source.Save(path, source.DetectEncoder(path)); /// /// Writes the image to the given stream using the currently loaded image format. @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp /// The path is null. /// A representing the asynchronous operation. public static Task SaveAsync(this Image source, string path) - => source.SaveAsync(path, source.FindEncoded(path)); + => source.SaveAsync(path, source.DetectEncoder(path)); /// /// Writes the image to the given stream using the currently loaded image format. diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index a327861ca..0aba932ce 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests { using (var image = new Image(5, 5)) { - IImageEncoder encoder = image.FindEncoded(filename); + IImageEncoder encoder = image.DetectEncoder(filename); using (var stream = new MemoryStream()) { var asyncStream = new AsyncStreamWrapper(stream, () => false); From d2a2709c710bf799e8dc6cf5ed431f42e97f24c9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 18:06:40 +0100 Subject: [PATCH 19/22] Revert unrequired async/await additions --- .../Advanced/AdvancedImageExtensions.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 4 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 4 +- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 4 +- src/ImageSharp/Formats/Tga/TgaEncoder.cs | 4 +- src/ImageSharp/Image.FromStream.cs | 37 +++++++++---------- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 61dfc8944..fea0feb53 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -74,8 +74,8 @@ namespace SixLabors.ImageSharp.Advanced /// The source image. /// The image visitor. /// A representing the asynchronous operation. - public static async Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) - => await source.AcceptAsync(visitor).ConfigureAwait(false); + public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) + => source.AcceptAsync(visitor); /// /// Gets the configuration for the image. diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 70023ffc1..52b87ef27 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -42,11 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public async Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); - await encoder.EncodeAsync(image, stream).ConfigureAwait(false); + return encoder.EncodeAsync(image, stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 78e78a81f..8d4c33eff 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public async Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new GifEncoderCore(image.GetConfiguration(), this); - await encoder.EncodeAsync(image, stream).ConfigureAwait(false); + return encoder.EncodeAsync(image, stream); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index f977623f4..5cd7ca7b0 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -74,11 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - return await new TgaDecoderCore(configuration, this).IdentifyAsync(stream).ConfigureAwait(false); + return new TgaDecoderCore(configuration, this).IdentifyAsync(stream); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index 449a724bf..058dd3559 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public async Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream) where TPixel : unmanaged, IPixel { var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); - await encoder.EncodeAsync(image, stream).ConfigureAwait(false); + return encoder.EncodeAsync(image, stream); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index bb36b1462..afd0f0e59 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -57,12 +57,11 @@ namespace SixLabors.ImageSharp /// The stream is null. /// The stream is not readable. /// The format type or null if none found. - public static async Task DetectFormatAsync(Configuration configuration, Stream stream) - => await WithSeekableStreamAsync( + public static Task DetectFormatAsync(Configuration configuration, Stream stream) + => WithSeekableStreamAsync( configuration, stream, - async s => await InternalDetectFormatAsync(s, configuration).ConfigureAwait(false)) - .ConfigureAwait(false); + s => InternalDetectFormatAsync(s, configuration)); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -184,12 +183,11 @@ namespace SixLabors.ImageSharp /// /// The with set to null if suitable info detector is not found. /// - public static async Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) - => await WithSeekableStreamAsync( + public static Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) + => WithSeekableStreamAsync( configuration, stream, - async s => await InternalIdentityAsync(s, configuration ?? Configuration.Default)) - .ConfigureAwait(false); + s => InternalIdentityAsync(s, configuration ?? Configuration.Default)); /// /// Decode a new instance of the class from the given stream. @@ -309,7 +307,10 @@ namespace SixLabors.ImageSharp public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) { Guard.NotNull(decoder, nameof(decoder)); - return WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync(configuration, s)); + return WithSeekableStreamAsync( + configuration, + stream, + s => decoder.DecodeAsync(configuration, s)); } /// @@ -425,13 +426,12 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new .> - public static async Task> LoadAsync(Stream stream, IImageDecoder decoder) + public static Task> LoadAsync(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => await WithSeekableStreamAsync( + => WithSeekableStreamAsync( Configuration.Default, stream, - async s => await decoder.DecodeAsync(Configuration.Default, s).ConfigureAwait(false)) - .ConfigureAwait(false); + s => decoder.DecodeAsync(Configuration.Default, s)); /// /// Create a new instance of the class from the given stream. @@ -463,13 +463,12 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new .> - public static async Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + public static Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => await WithSeekableStreamAsync( + => WithSeekableStreamAsync( configuration, stream, - async s => await decoder.DecodeAsync(configuration, s).ConfigureAwait(false)) - .ConfigureAwait(false); + s => decoder.DecodeAsync(configuration, s)); /// /// Create a new instance of the class from the given stream. @@ -577,8 +576,8 @@ namespace SixLabors.ImageSharp await WithSeekableStreamAsync( configuration, stream, - async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) - .ConfigureAwait(false); + async s => await DecodeAsync(s, configuration) + .ConfigureAwait(false)); if (data.img != null) { From 30c3c51586a5b84feccd57b293cba69ea18811ec Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 May 2020 23:49:13 +0100 Subject: [PATCH 20/22] Update Image.FromStream.cs --- src/ImageSharp/Image.FromStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index afd0f0e59..3676823dc 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -576,8 +576,8 @@ namespace SixLabors.ImageSharp await WithSeekableStreamAsync( configuration, stream, - async s => await DecodeAsync(s, configuration) - .ConfigureAwait(false)); + s => DecodeAsync(s, configuration)) + .ConfigureAwait(false); if (data.img != null) { From 6af07ce0d443fb994885f15e8264fda407e8eb1c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 Jun 2020 21:04:35 +0100 Subject: [PATCH 21/22] Use named tuple --- src/ImageSharp/FormattedImage.cs | 95 ------------------ src/ImageSharp/FormattedImageInfo.cs | 93 ------------------ src/ImageSharp/FormattedImage{TPixel}.cs | 98 ------------------- src/ImageSharp/Image.Decode.cs | 35 ++++--- src/ImageSharp/Image.FromStream.cs | 98 ++++++++++--------- .../Image/ImageTests.Identify.cs | 6 +- .../Image/ImageTests.SaveAsync.cs | 7 +- .../TestUtilities/AsyncStreamWrapper.cs | 6 +- 8 files changed, 76 insertions(+), 362 deletions(-) delete mode 100644 src/ImageSharp/FormattedImage.cs delete mode 100644 src/ImageSharp/FormattedImageInfo.cs delete mode 100644 src/ImageSharp/FormattedImage{TPixel}.cs diff --git a/src/ImageSharp/FormattedImage.cs b/src/ImageSharp/FormattedImage.cs deleted file mode 100644 index 9b604eced..000000000 --- a/src/ImageSharp/FormattedImage.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. - -using System; -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats; - -namespace SixLabors.ImageSharp -{ - /// - /// Struct to curry and for return from async overloads. - /// - public readonly struct FormattedImage : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The . - /// The . - public FormattedImage(Image image, IImageFormat format) - { - this.Image = image; - this.Format = format; - } - - /// - /// Gets the Image. - /// - public readonly Image Image { get; } - - /// - /// Gets the Format. - /// - public readonly IImageFormat Format { get; } - - /// - /// Converts to . - /// - /// The to convert. - public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - => (value.Image, value.Format); - - /// - /// Converts to - /// - /// The to convert. - public static implicit operator FormattedImage((Image image, IImageFormat format) value) - => new FormattedImage(value.image, value.format); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(FormattedImage left, FormattedImage right) - => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(FormattedImage left, FormattedImage right) - => !(left == right); - - /// - public override bool Equals(object obj) - => obj is FormattedImage image && this.Equals(image); - - /// - public bool Equals(FormattedImage other) - => EqualityComparer.Default.Equals(this.Image, other.Image) - && EqualityComparer.Default.Equals(this.Format, other.Format); - - /// - public override int GetHashCode() => HashCode.Combine(this.Image, this.Format); - - /// - /// Deconstructs into component parts. - /// - /// The . - /// The . - public void Deconstruct(out Image image, out IImageFormat format) - { - image = this.Image; - format = this.Format; - } - } -} diff --git a/src/ImageSharp/FormattedImageInfo.cs b/src/ImageSharp/FormattedImageInfo.cs deleted file mode 100644 index 72368be34..000000000 --- a/src/ImageSharp/FormattedImageInfo.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. - -using System; -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats; - -namespace SixLabors.ImageSharp -{ - /// - /// Struct to curry and for return from async overloads. - /// - public readonly struct FormattedImageInfo : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The . - /// The . - public FormattedImageInfo(IImageInfo imageInfo, IImageFormat format) - { - this.ImageInfo = imageInfo; - this.Format = format; - } - - /// - /// Gets the Image Info. - /// - public readonly IImageInfo ImageInfo { get; } - - /// - /// Gets the Format. - /// - public readonly IImageFormat Format { get; } - - /// - /// Converts to a - /// - /// The to convert. - public static implicit operator (IImageInfo imageInfo, IImageFormat format)(FormattedImageInfo value) - => (value.ImageInfo, value.Format); - - /// - /// Converts to - /// - /// The to convert. - public static implicit operator FormattedImageInfo((IImageInfo imageInfo, IImageFormat format) value) - => new FormattedImageInfo(value.imageInfo, value.format); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(FormattedImageInfo left, FormattedImageInfo right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(FormattedImageInfo left, FormattedImageInfo right) => !(left == right); - - /// - public override bool Equals(object obj) - => obj is FormattedImageInfo info && this.Equals(info); - - /// - public bool Equals(FormattedImageInfo other) - => EqualityComparer.Default.Equals(this.ImageInfo, other.ImageInfo) - && EqualityComparer.Default.Equals(this.Format, other.Format); - - /// - public override int GetHashCode() => HashCode.Combine(this.ImageInfo, this.Format); - - /// - /// Deconstructs into component parts. - /// - /// The . - /// The . - public void Deconstruct(out IImageInfo imageInfo, out IImageFormat format) - { - imageInfo = this.ImageInfo; - format = this.Format; - } - } -} diff --git a/src/ImageSharp/FormattedImage{TPixel}.cs b/src/ImageSharp/FormattedImage{TPixel}.cs deleted file mode 100644 index bb3eeabe7..000000000 --- a/src/ImageSharp/FormattedImage{TPixel}.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. - -using System; -using System.Collections.Generic; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Struct to curry and for return from async overloads. - /// - /// The pixel format. - public readonly struct FormattedImage : IEquatable> - where TPixel : unmanaged, IPixel - { - /// - /// Initializes a new instance of the struct. - /// - /// The . - /// The . - public FormattedImage(Image image, IImageFormat format) - { - this.Image = image; - this.Format = format; - } - - /// - /// Gets the Image. - /// - public readonly Image Image { get; } - - /// - /// Gets the Format. - /// - public readonly IImageFormat Format { get; } - - /// - /// Converts to . - /// - /// The to convert. - public static implicit operator (Image image, IImageFormat format)(FormattedImage value) - => (value.Image, value.Format); - - /// - /// Converts to - /// - /// The to convert. - public static implicit operator FormattedImage((Image image, IImageFormat format) value) - => new FormattedImage(value.image, value.format); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(FormattedImage left, FormattedImage right) - => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(FormattedImage left, FormattedImage right) - => !(left == right); - - /// - public override bool Equals(object obj) - => obj is FormattedImage image && this.Equals(image); - - /// - public bool Equals(FormattedImage other) - => EqualityComparer>.Default.Equals(this.Image, other.Image) - && EqualityComparer.Default.Equals(this.Format, other.Format); - - /// - public override int GetHashCode() => HashCode.Combine(this.Image, this.Format); - - /// - /// Deconstructs into component parts. - /// - /// The . - /// The . - public void Deconstruct(out Image image, out IImageFormat format) - { - image = this.Image; - format = this.Format; - } - } -} diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index e3ef5cb53..6b6dc6e21 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp /// /// A new . /// - private static FormattedImage Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp } Image img = decoder.Decode(config, stream); - return new FormattedImage(img, format); + return (img, format); } /// @@ -129,10 +129,8 @@ namespace SixLabors.ImageSharp /// The stream. /// the configuration. /// The pixel format. - /// - /// A new . - /// - private static async Task> DecodeAsync(Stream stream, Configuration config) + /// A representing the asynchronous operation. + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config) where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -142,10 +140,10 @@ namespace SixLabors.ImageSharp } Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); - return new FormattedImage(img, format); + return (img, format); } - private static FormattedImage Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -154,10 +152,10 @@ namespace SixLabors.ImageSharp } Image img = decoder.Decode(config, stream); - return new FormattedImage(img, format); + return (img, format); } - private static async Task DecodeAsync(Stream stream, Configuration config) + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -166,7 +164,7 @@ namespace SixLabors.ImageSharp } Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); - return new FormattedImage(img, format); + return (img, format); } /// @@ -175,9 +173,9 @@ namespace SixLabors.ImageSharp /// The stream. /// the configuration. /// - /// The or null if suitable info detector not found. + /// The or null if a suitable info detector is not found. /// - private static FormattedImageInfo InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config) { if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) { @@ -185,7 +183,7 @@ namespace SixLabors.ImageSharp } IImageInfo info = detector?.Identify(config, stream); - return new FormattedImageInfo(info, format); + return (info, format); } /// @@ -194,9 +192,10 @@ namespace SixLabors.ImageSharp /// The stream. /// the configuration. /// - /// The or null if suitable info detector not found. - /// - private static async Task InternalIdentityAsync(Stream stream, Configuration config) + /// A representing the asynchronous operation with the + /// property of the returned type set to null if a suitable detector + /// is not found. + private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config) { if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) { @@ -209,7 +208,7 @@ namespace SixLabors.ImageSharp } IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false); - return new FormattedImageInfo(info, format); + return (info, format); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 4f1d5c194..5c64ae559 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp /// The image stream to read the header from. /// The stream is null. /// The stream is not readable. - /// The format type or null if none found. + /// A representing the asynchronous operation or null if none is found. public static Task DetectFormatAsync(Stream stream) => DetectFormatAsync(Configuration.Default, stream); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp /// The configuration is null. /// The stream is null. /// The stream is not readable. - /// The format type or null if none found. + /// A representing the asynchronous operation. public static Task DetectFormatAsync(Configuration configuration, Stream stream) => WithSeekableStreamAsync( configuration, @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector not found. + /// The or null if a suitable info detector is not found. /// public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _); @@ -84,7 +84,8 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector not found. + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. /// public static Task IdentifyAsync(Stream stream) => IdentifyAsync(Configuration.Default, stream); @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector not found. + /// The or null if a suitable info detector is not found. /// public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format); @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector is not found. + /// The or null if a suitable info detector is not found. /// public static IImageInfo Identify(Configuration configuration, Stream stream) => Identify(configuration, stream, out _); @@ -128,11 +129,12 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector is not found. + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. /// public static async Task IdentifyAsync(Configuration configuration, Stream stream) { - FormattedImageInfo res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); return res.ImageInfo; } @@ -147,11 +149,11 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The or null if suitable info detector is not found. + /// The or null if a suitable info detector is not found. /// public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) { - FormattedImageInfo data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); + (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); format = data.Format; return data.ImageInfo; @@ -166,9 +168,10 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The with set to null if suitable info detector is not found. + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. /// - public static Task IdentifyWithFormatAsync(Stream stream) + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Stream stream) => IdentifyWithFormatAsync(Configuration.Default, stream); /// @@ -181,9 +184,10 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The with set to null if suitable info detector is not found. + /// The representing the asyncronous operation with the parameter type + /// property set to null if suitable info detector is not found. /// - public static Task IdentifyWithFormatAsync(Configuration configuration, Stream stream) + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream) => WithSeekableStreamAsync( configuration, stream, @@ -212,8 +216,8 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadWithFormatAsync(Stream stream) + /// A representing the asynchronous operation. + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) => LoadWithFormatAsync(Configuration.Default, stream); /// @@ -237,7 +241,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// The . + /// A representing the asynchronous operation. public static Task LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream); /// @@ -266,7 +270,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// The . + /// A representing the asynchronous operation. public static Task LoadAsync(Stream stream, IImageDecoder decoder) => LoadAsync(Configuration.Default, stream, decoder); @@ -283,7 +287,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A new .> + /// A new . public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) { Guard.NotNull(decoder, nameof(decoder)); @@ -303,7 +307,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A new .> + /// A representing the asynchronous operation. public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) { Guard.NotNull(decoder, nameof(decoder)); @@ -323,7 +327,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A new .> + /// A new . public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); /// @@ -336,10 +340,10 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A new .> + /// A representing the asynchronous operation. public static async Task LoadAsync(Configuration configuration, Stream stream) { - FormattedImage fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); + (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); return fmt.Image; } @@ -352,7 +356,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream) where TPixel : unmanaged, IPixel => Load(Configuration.Default, stream); @@ -366,7 +370,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A representing the asynchronous operation. public static Task> LoadAsync(Stream stream) where TPixel : unmanaged, IPixel => LoadAsync(Configuration.Default, stream); @@ -381,7 +385,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream, out IImageFormat format) where TPixel : unmanaged, IPixel => Load(Configuration.Default, stream, out format); @@ -395,8 +399,8 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A representing the asynchronous operation. - public static async Task> LoadWithFormatAsync(Stream stream) + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) where TPixel : unmanaged, IPixel => await LoadWithFormatAsync(Configuration.Default, stream).ConfigureAwait(false); @@ -410,7 +414,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); @@ -425,7 +429,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A representing the asynchronous operation. public static Task> LoadAsync(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( @@ -445,7 +449,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); @@ -462,7 +466,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A representing the asynchronous operation. public static Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( @@ -481,7 +485,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new .> + /// A new . public static Image Load(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel => Load(configuration, stream, out IImageFormat _); @@ -498,17 +502,17 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new . + /// A representing the asynchronous operation. public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) where TPixel : unmanaged, IPixel { - (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); + (Image Image, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); - format = data.format; + format = data.Format; - if (data.img != null) + if (data.Image != null) { - return data.img; + return data.Image; } var sb = new StringBuilder(); @@ -532,16 +536,16 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image format not recognised. /// Image contains invalid content. - /// A new . - public static async Task LoadWithFormatAsync(Configuration configuration, Stream stream) + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream) { - (Image img, IImageFormat format) data = await WithSeekableStreamAsync( + (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) .ConfigureAwait(false); - if (data.img != null) + if (data.Image != null) { return data; } @@ -568,18 +572,18 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new . - public static async Task> LoadWithFormatAsync(Configuration configuration, Stream stream) + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { - (Image img, IImageFormat format) data = + (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, s => DecodeAsync(s, configuration)) .ConfigureAwait(false); - if (data.img != null) + if (data.Image != null) { return data; } @@ -606,7 +610,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The pixel format. - /// A new . + /// A representing the asynchronous operation. public static async Task> LoadAsync(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 35e20dc0d..69b1d21a6 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests using (var stream = new MemoryStream(this.ActualImageBytes)) { var asyncStream = new AsyncStreamWrapper(stream, () => false); - FormattedImageInfo info = await Image.IdentifyWithFormatAsync(asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(asyncStream); Assert.NotNull(info.ImageInfo); Assert.Equal(ExpectedGlobalFormat, info.Format); @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Tests public async Task FromStreamAsync_CustomConfiguration() { var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - FormattedImageInfo info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.Equal(this.LocalImageFormat, info.Format); @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Tests public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() { var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - FormattedImageInfo info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); Assert.Null(info.ImageInfo); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 0aba932ce..4a6c96ae8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -1,5 +1,5 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; using System.IO; @@ -13,7 +13,6 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -27,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests public async Task DetectedEncoding() { string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = System.IO.Path.Combine(dir, "DetectedEncodingAsync.png"); + string file = Path.Combine(dir, "DetectedEncodingAsync.png"); using (var image = new Image(10, 10)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs index 3d23a3c3b..2000c6e0c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs @@ -1,10 +1,8 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the GNU Affero General Public License, Version 3. +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; From 089b41e2a976d19f1617aabfa23f8b042221a12e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 Jun 2020 23:47:52 +0100 Subject: [PATCH 22/22] 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 {