diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs
index cb6f01ce49..c6f9b82241 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 bcd11845bb..fa4b30d65a 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 ab3a87a315..9a7960f0fd 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