Browse Source

IdentifyAsync

+ stylecop
pull/1196/head
Scott Williams 6 years ago
parent
commit
c69683e122
  1. 1
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 1
      src/ImageSharp/Advanced/IImageVisitor.cs
  3. 1
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 1
      src/ImageSharp/Formats/IImageEncoder.cs
  5. 1
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  6. 1
      src/ImageSharp/Formats/Png/PngEncoder.cs
  7. 79
      src/ImageSharp/FormattedImage.cs
  8. 79
      src/ImageSharp/FormattedImageInfo.cs
  9. 82
      src/ImageSharp/FormattedImage{TPixel}.cs
  10. 38
      src/ImageSharp/Image.Decode.cs
  11. 195
      src/ImageSharp/Image.FromStream.cs
  12. 1
      src/ImageSharp/Image.cs
  13. 3
      src/ImageSharp/ImageExtensions.cs
  14. 27
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  15. 66
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  16. 1
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  17. 1
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  18. 3
      tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs

1
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -75,6 +75,7 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <param name="source">The source.</param>
/// <param name="visitor">The visitor.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
=> source.AcceptAsync(visitor);

1
src/ImageSharp/Advanced/IImageVisitor.cs

@ -32,6 +32,7 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task VisitAsync<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
}

1
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -76,7 +76,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
return decoder.Identify(stream);
}
/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{

1
src/ImageSharp/Formats/IImageEncoder.cs

@ -27,6 +27,7 @@ namespace SixLabors.ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
}

1
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -43,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{

1
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{

79
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
{
/// <summary>
/// Struct to curry <see cref="ImageSharp.Image"/> and <see cref="IImageFormat"/> for return from async overloads.
/// </summary>
public readonly struct FormattedImage
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedImage"/> struct.
/// </summary>
/// <param name="image">The <see cref="FormattedImage"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public FormattedImage(Image image, IImageFormat format)
{
this.Image = image;
this.Format = format;
}
/// <summary>
/// Gets the Image.
/// </summary>
public readonly Image Image { get; }
/// <summary>
/// Gets the Format.
/// </summary>
public readonly IImageFormat Format { get; }
/// <summary>
/// Converts <see cref="FormattedImage"/> to <see cref="ValueTuple"/>
/// </summary>
/// <param name="value">The <see cref="FormattedImage"/> to convert.</param>
public static implicit operator (Image image, IImageFormat format)(FormattedImage value)
{
return (value.Image, value.Format);
}
/// <summary>
/// Converts <see cref="ValueTuple"/> to <see cref="FormattedImage"/>
/// </summary>
/// <param name="value">The <see cref="ValueTuple"/> to convert.</param>
public static implicit operator FormattedImage((Image image, IImageFormat format) value)
{
return new FormattedImage(value.image, value.format);
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(this.Image, this.Format);
}
/// <summary>
/// Deconstructs <see cref="FormattedImage"/> into component parts.
/// </summary>
/// <param name="image">The <see cref="ImageSharp.Image"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public void Deconstruct(out Image image, out IImageFormat format)
{
image = this.Image;
format = this.Format;
}
}
}

79
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
{
/// <summary>
/// Struct to curry <see cref="IImageInfo"/> and <see cref="IImageFormat"/> for return from async overloads.
/// </summary>
public readonly struct FormattedImageInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedImageInfo"/> struct.
/// </summary>
/// <param name="imageInfo">The <see cref="FormattedImageInfo"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public FormattedImageInfo(IImageInfo imageInfo, IImageFormat format)
{
this.ImageInfo = imageInfo;
this.Format = format;
}
/// <summary>
/// Gets the Image Info.
/// </summary>
public readonly IImageInfo ImageInfo { get; }
/// <summary>
/// Gets the Format.
/// </summary>
public readonly IImageFormat Format { get; }
/// <summary>
/// Converts <see cref="FormattedImageInfo"/> to a <see cref="ValueTuple"/>
/// </summary>
/// <param name="value">The <see cref="FormattedImageInfo"/> to convert.</param>
public static implicit operator (IImageInfo imageInfo, IImageFormat format)(FormattedImageInfo value)
{
return (value.ImageInfo, value.Format);
}
/// <summary>
/// Converts <see cref="ValueTuple"/> to <see cref="FormattedImageInfo"/>
/// </summary>
/// <param name="value">The <see cref="ValueTuple"/> to convert.</param>
public static implicit operator FormattedImageInfo((IImageInfo imageInfo, IImageFormat format) value)
{
return new FormattedImageInfo(value.imageInfo, value.format);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is FormattedImageInfo other &&
EqualityComparer<IImageInfo>.Default.Equals(this.ImageInfo, other.ImageInfo) &&
EqualityComparer<IImageFormat>.Default.Equals(this.Format, other.Format);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(this.ImageInfo, this.Format);
}
/// <summary>
/// Deconstructs <see cref="FormattedImageInfo"/> into component parts.
/// </summary>
/// <param name="imageInfo">The <see cref="FormattedImageInfo"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public void Deconstruct(out IImageInfo imageInfo, out IImageFormat format)
{
imageInfo = this.ImageInfo;
format = this.Format;
}
}
}

82
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
{
/// <summary>
/// Struct to curry <see cref="ImageSharp.Image{TPixel}"/> and <see cref="IImageFormat"/> for return from async overloads.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public readonly struct FormattedImage<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedImage{TPixel}"/> struct.
/// </summary>
/// <param name="image">The <see cref="ImageSharp.Image{TPixel}"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public FormattedImage(Image<TPixel> image, IImageFormat format)
{
this.Image = image;
this.Format = format;
}
/// <summary>
/// Gets the Image.
/// </summary>
public readonly Image<TPixel> Image { get; }
/// <summary>
/// Gets the Format.
/// </summary>
public readonly IImageFormat Format { get; }
/// <summary>
/// Converts <see cref="FormattedImage{TPixel}"/> to <see cref="ValueTuple"/>.
/// </summary>
/// <param name="value">The <see cref="FormattedImage{TPixel}"/> to convert.</param>
public static implicit operator (Image<TPixel> image, IImageFormat format)(FormattedImage<TPixel> value)
{
return (value.Image, value.Format);
}
/// <summary>
/// Converts <see cref="ValueTuple"/> to <see cref="FormattedImage{TPixel}"/>
/// </summary>
/// <param name="value">The <see cref="ValueTuple"/> to convert.</param>
public static implicit operator FormattedImage<TPixel>((Image<TPixel> image, IImageFormat format) value)
{
return new FormattedImage<TPixel>(value.image, value.format);
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(this.Image, this.Format);
}
/// <summary>
/// Deconstructs <see cref="FormattedImage"/> into component parts.
/// </summary>
/// <param name="image">The <see cref="ImageSharp.Image{TPixel}"/>.</param>
/// <param name="format">The <see cref="IImageFormat"/>.</param>
public void Deconstruct(out Image<TPixel> image, out IImageFormat format)
{
image = this.Image;
format = this.Format;
}
}
}

38
src/ImageSharp/Image.Decode.cs

@ -71,6 +71,20 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="config">The configuration.</param>
/// <returns>The mime type or null if none found.</returns>
private static Task<IImageFormat> 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));
}
/// <summary>
/// By reading the header on the provided stream this calculates the images format.
/// </summary>
@ -163,14 +177,34 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
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);
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="config">the configuration.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
private static async Task<FormattedImageInfo> 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);
}
}
}

195
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));
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns>
public static Task<IImageFormat> DetectFormatAsync(Stream stream)
=> DetectFormatAsync(Configuration.Default, stream);
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</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>
/// <returns>The format type or null if none found.</returns>
public static Task<IImageFormat> DetectFormatAsync(Configuration configuration, Stream stream)
=> WithSeekableStreamAsync(configuration, stream, s => InternalDetectFormatAsync(s, configuration));
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
@ -51,6 +73,19 @@ namespace SixLabors.ImageSharp
public static IImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static Task<IImageInfo> IdentifyAsync(Stream stream)
=> IdentifyAsync(Configuration.Default, stream);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
@ -65,6 +100,39 @@ namespace SixLabors.ImageSharp
public static IImageInfo Identify(Stream stream, out IImageFormat format)
=> Identify(Configuration.Default, stream, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, Stream stream)
=> Identify(configuration, stream, out _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
FormattedImageInfo res = await IdentifyWithFormatAsync(configuration, stream);
return res.ImageInfo;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
@ -80,12 +148,41 @@ namespace SixLabors.ImageSharp
/// </returns>
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;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the information from.</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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="FormattedImageInfo"/> with <see cref="FormattedImageInfo.ImageInfo"/> set to null if suitable info detector is not found.
/// </returns>
public static Task<FormattedImageInfo> IdentifyWithFormatAsync(Stream stream)
=> IdentifyWithFormatAsync(Configuration.Default, stream);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="FormattedImageInfo"/> with <see cref="FormattedImageInfo.ImageInfo"/> set to null if suitable info detector is not found.
/// </returns>
public static Task<FormattedImageInfo> IdentifyWithFormatAsync(Configuration configuration, Stream stream)
=> WithSeekableStreamAsync(configuration, stream, s => InternalIdentityAsync(s, configuration ?? Configuration.Default));
/// <summary>
/// Decode a new instance of the <see cref="Image"/> 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);
/// <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>
/// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns>
public static Task<FormattedImage> LoadWithFormatAsync(Stream stream)
=> LoadWithFormatAsync(Configuration.Default, stream);
@ -282,18 +377,16 @@ 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>>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<FormattedImage<TPixel>> LoadWithFormatAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadWithFormatAsync<TPixel>(Configuration.Default, stream);
@ -594,88 +687,4 @@ namespace SixLabors.ImageSharp
}
}
}
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;
}
}
}

1
src/ImageSharp/Image.cs

@ -104,6 +104,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SaveAsync(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));

3
src/ImageSharp/ImageExtensions.cs

@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsync(this Image source, string path)
=> source.SaveAsync(path, source.FindEncoded(path));
@ -52,7 +53,6 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
@ -61,6 +61,7 @@ namespace SixLabors.ImageSharp
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The encoder is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task SaveAsync(this Image source, string path, IImageEncoder encoder)
{
Guard.NotNull(path, nameof(path));

27
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);
}
}
}
}

66
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);
}
}
}
}

1
tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs

@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests
var detector = new Mock<IImageInfoDetector>();
detector.Setup(x => x.Identify(It.IsAny<Configuration>(), It.IsAny<Stream>())).Returns(this.localImageInfoMock.Object);
detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>())).ReturnsAsync(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);

1
tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs

@ -23,7 +23,6 @@ namespace SixLabors.ImageSharp.Tests
{
public class SaveAsync
{
[Fact]
public async Task DetectedEncoding()
{

3
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;

Loading…
Cancel
Save