Browse Source

implement Load Async apis

pull/1196/head
Scott Williams 6 years ago
parent
commit
dff8ab68e7
  1. 45
      src/ImageSharp/Image.Decode.cs
  2. 341
      src/ImageSharp/Image.FromStream.cs
  3. 82
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs
  4. 0
      tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs

45
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
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </returns>
private static (Image<TPixel> img, IImageFormat format) Decode<TPixel>(Stream stream, Configuration config)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
private static FormattedImage<TPixel> Decode<TPixel>(Stream stream, Configuration config)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
@ -107,10 +106,32 @@ namespace SixLabors.ImageSharp
}
Image<TPixel> img = decoder.Decode<TPixel>(config, stream);
return (img, format);
return new FormattedImage<TPixel>(img, format);
}
private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config)
/// <summary>
/// Decodes the image stream to the current image.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </returns>
private static async Task<FormattedImage<TPixel>> DecodeAsync<TPixel>(Stream stream, Configuration config)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream);
return new FormattedImage<TPixel>(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<FormattedImage> 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);
}
/// <summary>

341
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);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Task<FormattedImage> LoadWithFormatAsync(Stream stream)
=> LoadWithFormatAsync(Configuration.Default, stream);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
@ -111,6 +127,18 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream) => Load(Configuration.Default, stream);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Task<Image> LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> 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);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Task<Image> LoadAsync(Stream stream, IImageDecoder decoder)
=> LoadAsync(Configuration.Default, stream, decoder);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> 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));
}
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
public static Task<Image> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync(configuration, s));
}
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
@ -159,6 +222,23 @@ namespace SixLabors.ImageSharp
/// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
public static async Task<Image> LoadAsync(Configuration configuration, Stream stream)
{
var fmt = await LoadWithFormatAsync(configuration, stream);
return fmt.Image;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -173,6 +253,20 @@ namespace SixLabors.ImageSharp
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, stream);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(Configuration.Default, stream);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -188,6 +282,22 @@ namespace SixLabors.ImageSharp
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, stream, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Task<FormattedImage<TPixel>> LoadWithFormatAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadWithFormatAsync<TPixel>(Configuration.Default, stream);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -203,6 +313,21 @@ namespace SixLabors.ImageSharp
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(Configuration.Default, stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(Configuration.Default, stream, s => decoder.DecodeAsync<TPixel>(Configuration.Default, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -220,6 +345,23 @@ namespace SixLabors.ImageSharp
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(configuration, stream, s => decoder.Decode<TPixel>(configuration, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(configuration, stream, s => decoder.DecodeAsync<TPixel>(configuration, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -272,6 +414,89 @@ namespace SixLabors.ImageSharp
throw new UnknownImageFormatException(sb.ToString());
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static async Task<FormattedImage> 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<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static async Task<FormattedImage<TPixel>> LoadWithFormatAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> img, IImageFormat format) data = await WithSeekableStreamAsync(configuration, stream, s => DecodeAsync<TPixel>(s, configuration));
if (data.img != null)
{
return data;
}
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(configuration, stream);
return img;
}
/// <summary>
/// Decode a new instance of the <see cref="Image"/> 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<T> WithSeekableStreamAsync<T>(Configuration configuration, Stream stream, Func<Stream, Task<T>> 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<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{
public FormattedImage(Image<TPixel> image, IImageFormat format)
{
this.Image = image;
this.Format = format;
}
public readonly Image<TPixel> Image { get; }
public readonly IImageFormat Format { get; }
public static implicit operator (Image<TPixel> image, IImageFormat format)(FormattedImage<TPixel> value)
{
return (value.Image, value.Format);
}
public static implicit operator FormattedImage<TPixel>((Image<TPixel> image, IImageFormat format) value)
{
return new FormattedImage<TPixel>(value.image, value.format);
}
public override bool Equals(object obj)
{
return obj is FormattedImage<TPixel> other &&
EqualityComparer<Image<TPixel>>.Default.Equals(this.Image, other.Image) &&
EqualityComparer<IImageFormat>.Default.Equals(this.Format, other.Format);
}
public override int GetHashCode()
{
return HashCode.Combine(this.Image, this.Format);
}
public void Deconstruct(out Image<TPixel> 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<Image>.Default.Equals(this.Image, other.Image) &&
EqualityComparer<IImageFormat>.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;
}
}
}

82
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<BmpFormat>(formattedImage.Format);
}
}
[Fact]
public async Task Async_Stream_Specific()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync<Rgba32>(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<Rgba32>(this.Stream);
using (formattedImage.Image)
{
VerifyDecodedImage(formattedImage.Image);
Assert.IsType<BmpFormat>(formattedImage.Format);
}
}
[Fact]
public async Task Async_Stream_Decoder_Specific()
{
this.AllowSynchronousIO = false;
using (var img = await Image.LoadAsync<Rgba32>(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();
}
}
}

0
tests/ImageSharp.Tests/TestUtilities/AsyncOnlyStream.cs → tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs

Loading…
Cancel
Save