Browse Source

Merge pull request #1196 from SixLabors/sw/fake-async-codecs

Async APIs / Fake Async Codecs
pull/1574/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
7233ed653c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 63
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 17
      src/ImageSharp/Advanced/IImageVisitor.cs
  3. 32
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  4. 50
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  5. 13
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  6. 28
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  7. 34
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  8. 50
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  9. 9
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  10. 20
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  11. 21
      src/ImageSharp/Formats/IImageDecoder.cs
  12. 13
      src/ImageSharp/Formats/IImageEncoder.cs
  13. 13
      src/ImageSharp/Formats/IImageInfoDetector.cs
  14. 38
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  15. 48
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  16. 31
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  17. 3
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  18. 57
      src/ImageSharp/Formats/Png/PngDecoder.cs
  19. 56
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  20. 17
      src/ImageSharp/Formats/Png/PngEncoder.cs
  21. 29
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  22. 35
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  23. 53
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  24. 10
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  25. 26
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  26. 50
      src/ImageSharp/IO/FixedCapacityPooledMemoryStream.cs
  27. 85
      src/ImageSharp/Image.Decode.cs
  28. 421
      src/ImageSharp/Image.FromStream.cs
  29. 35
      src/ImageSharp/Image.cs
  30. 58
      src/ImageSharp/ImageExtensions.cs
  31. 9
      src/ImageSharp/Image{TPixel}.cs
  32. 27
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  33. 66
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  34. 1
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  35. 82
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs
  36. 107
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  37. 17
      tests/ImageSharp.Tests/TestFormat.cs
  38. 118
      tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs
  39. 8
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  40. 12
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  41. 15
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs
  42. 32
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

63
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -15,15 +17,66 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
public static class AdvancedImageExtensions
{
/// <summary>
/// For a given file path find the best encoder to use via its extension.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="filePath">The target file path to save the image to.</param>
/// <returns>The matching encoder.</returns>
public static IImageEncoder DetectEncoder(this Image source, string filePath)
{
Guard.NotNull(filePath, nameof(filePath));
string ext = Path.GetExtension(filePath);
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<IImageFormat, IImageEncoder> 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;
}
/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source.</param>
/// <param name="visitor">The visitor.</param>
/// <param name="source">The source image.</param>
/// <param name="visitor">The image visitor.</param>
public static void AcceptVisitor(this Image source, IImageVisitor visitor)
=> source.Accept(visitor);
/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="visitor">The image visitor.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor)
=> source.AcceptAsync(visitor);
/// <summary>
/// Gets the configuration for the image.
/// </summary>

17
src/ImageSharp/Advanced/IImageVisitor.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Advanced
@ -19,4 +20,20 @@ namespace SixLabors.ImageSharp.Advanced
void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
}
/// <summary>
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="Image"/> instances.
/// </summary>
public interface IImageVisitorAsync
{
/// <summary>
/// Provides a pixel-specific implementation for a given operation.
/// </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>;
}
}

32
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -27,6 +28,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(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);
}
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
@ -50,6 +71,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
@ -57,5 +81,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
return new BmpDecoderCore(configuration, this).Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).IdentifyAsync(stream);
}
}
}

50
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -7,7 +7,9 @@ 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.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -130,8 +132,40 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// if we can seek then we arn't in a context that errors on async operations
if (stream.CanSeek)
{
return this.Decode<TPixel>(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<TPixel>(ms);
}
}
}
/// <summary>
/// Decodes the image from the specified this._stream and sets
/// the data to image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be
/// decoded from. Cannot be null (Nothing in Visual Basic).</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
@ -218,6 +252,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}
/// <summary>
/// Returns the y- value based on the given height.
/// </summary>

13
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream);
}
}
}
}

28
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,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));

34
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
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(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;
}
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
@ -54,7 +76,19 @@ namespace SixLabors.ImageSharp.Formats.Gif
return decoder.Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
return decoder.IdentifyAsync(stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
}
}

50
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -6,7 +6,8 @@ using System.IO;
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;
@ -103,8 +104,32 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image data.</param>
/// <returns>The decoded image</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
return this.Decode<TPixel>(stream);
}
else
{
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Decode<TPixel>(ms);
}
}
}
/// <summary>
/// Decodes the stream to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image data.</param>
/// <returns>The decoded image</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
@ -163,6 +188,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
return image;
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
if (stream.CanSeek)
{
return this.Identify(stream);
}
else
{
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>

9
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
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);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream);
}
}
}

20
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
/// <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>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <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>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));

21
src/ImageSharp/Formats/IImageDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -30,5 +31,25 @@ namespace SixLabors.ImageSharp.Formats
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/>.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Task<Image> DecodeAsync(Configuration configuration, Stream stream);
}
}

13
src/ImageSharp/Formats/IImageEncoder.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -19,5 +20,15 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <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>;
}
}

13
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
{
@ -17,5 +18,13 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
IImageInfo Identify(Configuration configuration, Stream stream);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream);
}
}
}

38
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -17,6 +18,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false);
}
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;
}
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
@ -43,6 +66,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
@ -53,5 +80,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return decoder.Identify(stream);
}
}
/// <inheritdoc/>
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new JpegDecoderCore(configuration, this))
{
return await decoder.IdentifyAsync(stream).ConfigureAwait(false);
}
}
}
}

48
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,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
return this.Decode<TPixel>(stream);
}
else
{
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Decode<TPixel>(ms);
}
}
}
/// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets the data to image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ParseStream(stream);
this.InitExifProfile();
@ -229,6 +254,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return this.PostProcessIntoImage<TPixel>();
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
if (stream.CanSeek)
{
return this.Identify(stream);
}
else
{
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>

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

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -35,5 +36,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream);
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <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>
{
var encoder = new JpegEncoderCore(this);
if (stream.CanSeek)
{
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);
}
}
}
}
}

3
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
/// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));

57
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -2,31 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Encoder for generating an image out of a png encoded stream.
/// Decoder for generating an image out of a png encoded stream.
/// </summary>
/// <remarks>
/// At the moment the following features are supported:
/// <para>
/// <b>Filters:</b> all filters are supported.
/// </para>
/// <para>
/// <b>Pixel formats:</b>
/// <list type="bullet">
/// <item>RGBA (True color) with alpha (8 bit).</item>
/// <item>RGB (True color) without alpha (8 bit).</item>
/// <item>grayscale with alpha (8 bit).</item>
/// <item>grayscale without alpha (8 bit).</item>
/// <item>Palette Index with alpha (8 bit).</item>
/// <item>Palette Index without alpha (8 bit).</item>
/// </list>
/// </para>
/// </remarks>
public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector
{
/// <summary>
@ -34,6 +18,33 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The decoded image.</returns>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(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;
}
}
/// <summary>
/// Decodes the image from the specified stream to the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
@ -68,7 +79,17 @@ namespace SixLabors.ImageSharp.Formats.Png
return decoder.Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
return decoder.IdentifyAsync(stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
}
}

56
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -9,10 +9,11 @@ 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;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -144,8 +145,38 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
return this.Decode<TPixel>(stream);
}
else
{
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Decode<TPixel>(ms);
}
}
}
/// <summary>
/// Decodes the stream to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image data.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the stream does not contain and end chunk.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata();
@ -235,6 +266,27 @@ namespace SixLabors.ImageSharp.Formats.Png
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
if (stream.CanSeek)
{
return this.Identify(stream);
}
else
{
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -63,5 +64,21 @@ namespace SixLabors.ImageSharp.Formats.Png
encoder.Encode(image, stream);
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <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>
{
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
{
await encoder.EncodeAsync(image, stream).ConfigureAwait(false);
}
}
}
}

29
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -7,7 +7,8 @@ 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;
using SixLabors.ImageSharp.Formats.Png.Zlib;
@ -126,8 +127,32 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));

35
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +13,29 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// </summary>
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
{
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
try
{
return await decoder.DecodeAsync<TPixel>(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;
}
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
@ -38,6 +62,9 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
@ -45,5 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
return new TgaDecoderCore(configuration, this).Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).IdentifyAsync(stream);
}
}
}

53
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -5,7 +5,8 @@ using System;
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;
@ -88,8 +89,35 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
return this.Decode<TPixel>(stream);
}
else
{
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Decode<TPixel>(ms);
}
}
}
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is null.</para>
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
@ -650,6 +678,27 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public async Task<IImageInfo> IdentifyAsync(Stream stream)
{
if (stream.CanSeek)
{
return this.Identify(stream);
}
else
{
using (var ms = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
return this.Identify(ms);
}
}
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>

10
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
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);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream);
}
}
}

26
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,30 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.compression = options.Compression;
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
if (stream.CanSeek)
{
this.Encode(image, stream);
}
else
{
using (var ms = new MemoryStream())
{
this.Encode(image, ms);
ms.Position = 0;
await ms.CopyToAsync(stream).ConfigureAwait(false);
}
}
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
/// </summary>

50
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
{
/// <summary>
/// A memory stream constructed from a pooled buffer of known length.
/// </summary>
internal sealed class FixedCapacityPooledMemoryStream : MemoryStream
{
private readonly byte[] buffer;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="FixedCapacityPooledMemoryStream"/> class.
/// </summary>
/// <param name="length">The length of the stream buffer to rent.</param>
public FixedCapacityPooledMemoryStream(long length)
: this(RentBuffer(length)) => this.Length = length;
private FixedCapacityPooledMemoryStream(byte[] buffer)
: base(buffer) => this.buffer = buffer;
/// <inheritdoc/>
public override long Length { get; }
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (!this.isDisposed)
{
this.isDisposed = true;
if (disposing)
{
ArrayPool<byte>.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<byte>.Shared.Rent((int)length);
}
}

85
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;
@ -70,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>
@ -86,7 +101,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 +110,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 (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(Stream stream, Configuration config)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
@ -110,7 +123,27 @@ namespace SixLabors.ImageSharp
return (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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
private static async Task<(Image<TPixel> Image, IImageFormat Format)> 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).ConfigureAwait(false);
return (img, format);
}
private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config)
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (decoder is null)
@ -122,22 +155,60 @@ namespace SixLabors.ImageSharp
return (img, format);
}
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)
{
return (null, null);
}
Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false);
return (img, 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.
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static (IImageInfo info, IImageFormat format) 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))
{
return (null, null);
}
IImageInfo info = detector?.Identify(config, stream);
return (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>
/// A <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the
/// <see cref="IImageInfo"/> property of the returned type set to null if a suitable detector
/// is not found.</returns>
private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config)
{
if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector))
{
return (null, null);
}
return (detector?.Identify(config, stream), format);
if (detector is null)
{
return (null, format);
}
IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false);
return (info, format);
}
}
}

421
src/ImageSharp/Image.FromStream.cs

@ -5,7 +5,9 @@ using System;
using System.Collections.Generic;
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
@ -37,6 +39,31 @@ 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>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation or null if none is 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>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</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>
@ -45,11 +72,25 @@ namespace SixLabors.ImageSharp
/// <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.
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
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>
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is 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>
@ -59,11 +100,45 @@ namespace SixLabors.ImageSharp
/// <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.
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
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 a 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>
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
{
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false);
return res.ImageInfo;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
@ -75,16 +150,50 @@ namespace SixLabors.ImageSharp
/// <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.
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </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));
(IImageInfo ImageInfo, IImageFormat Format) 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>
/// A <see cref="Task"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> 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="Task{ValueTuple}"/> representing the asyncronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> 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.
@ -99,6 +208,19 @@ 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>
/// <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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static Task<(Image Image, IImageFormat Format)> 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 +233,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>A <see cref="Task{Image}"/> representing the asynchronous operation.</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 +260,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>A <see cref="Task{Image}"/> representing the asynchronous operation.</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.
@ -139,13 +288,36 @@ namespace SixLabors.ImageSharp
/// <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>>
/// <returns>A new <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
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 <see cref="Task{Image}"/> representing the asynchronous operation.</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>
@ -156,9 +328,26 @@ namespace SixLabors.ImageSharp
/// <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>>
/// <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 <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(Configuration configuration, Stream stream)
{
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false);
return fmt.Image;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -168,11 +357,25 @@ namespace SixLabors.ImageSharp
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream)
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 <see cref="Task{Image}"/> representing the asynchronous operation.</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>
@ -183,11 +386,25 @@ namespace SixLabors.ImageSharp
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
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>
/// <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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> await LoadWithFormatAsync<TPixel>(Configuration.Default, stream).ConfigureAwait(false);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
@ -198,11 +415,29 @@ namespace SixLabors.ImageSharp
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
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 <see cref="Task"/> representing the asynchronous operation.</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>
@ -215,11 +450,31 @@ namespace SixLabors.ImageSharp
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
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 <see cref="Task"/> representing the asynchronous operation.</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>
@ -231,7 +486,7 @@ namespace SixLabors.ImageSharp
/// <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 new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(configuration, stream, out IImageFormat _);
@ -248,17 +503,17 @@ namespace SixLabors.ImageSharp
/// <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 Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode<TPixel>(s, configuration));
(Image<TPixel> Image, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode<TPixel>(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();
@ -272,6 +527,98 @@ 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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream)
{
(Image Image, IImageFormat Format) data = await WithSeekableStreamAsync(
configuration,
stream,
async s => await DecodeAsync(s, configuration).ConfigureAwait(false))
.ConfigureAwait(false);
if (data.Image != 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 <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync(
configuration,
stream,
s => DecodeAsync<TPixel>(s, configuration))
.ConfigureAwait(false);
if (data.Image != 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 <see cref="Task"/> representing the asynchronous operation.</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).ConfigureAwait(false);
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.
@ -328,7 +675,7 @@ namespace SixLabors.ImageSharp
}
// We want to be able to load images from things like HttpContext.Request.Body
using (var memoryStream = new MemoryStream())
using (var memoryStream = new FixedCapacityPooledMemoryStream(stream.Length))
{
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
@ -336,5 +683,41 @@ 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 without
// having to further wrap
if (stream.CanSeek)
{
if (configuration.ReadOrigin == ReadOrigin.Begin)
{
stream.Position = 0;
}
return await action(stream).ConfigureAwait(false);
}
using (var memoryStream = new FixedCapacityPooledMemoryStream(stream.Length))
{
await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
return await action(memoryStream).ConfigureAwait(false);
}
}
}
}

35
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,22 @@ namespace SixLabors.ImageSharp
this.AcceptVisitor(new EncodeVisitor(encoder, stream));
}
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <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 async Task SaveAsync(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder));
this.EnsureNotDisposed();
await this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)).ConfigureAwait(false);
}
/// <summary>
/// Returns a copy of the image in the given pixel format.
/// </summary>
@ -140,7 +156,15 @@ namespace SixLabors.ImageSharp
/// <param name="visitor">The visitor.</param>
internal abstract void Accept(IImageVisitor visitor);
private class EncodeVisitor : IImageVisitor
/// <summary>
/// Accepts a <see cref="IImageVisitor"/>.
/// Implemented by <see cref="Image{TPixel}"/> invoking <see cref="IImageVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary>
/// <param name="visitor">The visitor.</param>
internal abstract Task AcceptAsync(IImageVisitorAsync visitor);
private class EncodeVisitor : IImageVisitor, IImageVisitorAsync
{
private readonly IImageEncoder encoder;
@ -153,10 +177,11 @@ namespace SixLabors.ImageSharp
}
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> => this.encoder.Encode(image, this.stream);
public async Task VisitAsync<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
this.encoder.Encode(image, this.stream);
}
=> await this.encoder.EncodeAsync(image, this.stream).ConfigureAwait(false);
}
}
}

58
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,38 +23,34 @@ namespace SixLabors.ImageSharp
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
public static void Save(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());
}
=> source.Save(path, source.DetectEncoder(path));
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <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.DetectEncoder(path));
if (encoder is null)
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="path">The file path to save the image to.</param>
/// <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>
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<IImageFormat, IImageEncoder> 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);
}
/// <summary>
@ -64,13 +61,14 @@ 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>
public static void Save(this Image source, string path, IImageEncoder encoder)
/// <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));
Guard.NotNull(encoder, nameof(encoder));
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
source.Save(fs, encoder);
await source.SaveAsync(fs, encoder).ConfigureAwait(false);
}
}

9
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);
}
/// <inheritdoc />
internal override Task AcceptAsync(IImageVisitorAsync visitor)
{
this.EnsureNotDisposed();
return visitor.VisitAsync(this);
}
/// <summary>
/// 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.
/// </summary>

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 Apache License, Version 2.0.
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);
(IImageInfo ImageInfo, IImageFormat Format) 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);
(IImageInfo ImageInfo, IImageFormat Format) 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);
(IImageInfo ImageInfo, IImageFormat Format) 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);

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

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

@ -0,0 +1,107 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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.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 = Path.Combine(dir, "DetectedEncodingAsync.png");
using (var image = new Image<Rgba32>(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<NotSupportedException>(
async () =>
{
using (var image = new Image<Rgba32>(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<Rgba32>(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<Rgba32>(5, 5);
image.Dispose();
IImageEncoder encoder = Mock.Of<IImageEncoder>();
using (var stream = new MemoryStream())
{
await Assert.ThrowsAsync<ObjectDisposedException>(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<Rgba32>(5, 5))
{
IImageEncoder encoder = image.DetectEncoder(filename);
using (var stream = new MemoryStream())
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
await image.SaveAsync(asyncStream, encoder);
}
}
}
}
}
}

17
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<TPixel>();
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(config, stream));
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
public Image Decode(Configuration configuration, Stream stream) => this.Decode<TestPixelForAgnosticDecode>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<TestPixelForAgnosticDecode>(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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO record this happened so we can verify it.
return Task.CompletedTask;
}
}
public struct TestPixelForAgnosticDecode : IPixel<TestPixelForAgnosticDecode>

118
tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs

@ -0,0 +1,118 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
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<bool> allowSynchronousIO;
internal AsyncStreamWrapper(Stream inner, Func<bool> 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 => this.inner.Length;
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<int> 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
}
}
}

8
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<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -80,5 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream);
}
}

12
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

@ -2,7 +2,8 @@
// Licensed under the Apache License, Version 2.0.
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<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -43,6 +48,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<IImageInfo> 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<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream);
}
}

15
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image))
{
sdBitmap.Save(stream, this.imageFormat);
}
return Task.CompletedTask;
}
}
}

32
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<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -368,15 +371,27 @@ namespace SixLabors.ImageSharp.Tests
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(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<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream);
}
private class TestDecoderWithParameters : IImageDecoder
@ -384,6 +399,9 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary<string, int> InvocationCounts =
new ConcurrentDictionary<string, int>();
private static readonly ConcurrentDictionary<string, int> InvocationCountsAsync =
new ConcurrentDictionary<string, int>();
private static readonly object Monitor = new object();
private string callerName;
@ -407,15 +425,27 @@ namespace SixLabors.ImageSharp.Tests
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
return Task.FromResult(new Image<TPixel>(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<Rgba32>(configuration, stream);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream);
}
}
}

Loading…
Cancel
Save