Browse Source

add asynchronous decoder logic

pull/1574/head
Anton Firszov 6 years ago
parent
commit
9c465a7ff2
  1. 6
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  2. 47
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 5
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 46
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  5. 5
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  6. 7
      src/ImageSharp/Formats/IImageDecoder.cs
  7. 20
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  8. 4
      src/ImageSharp/Formats/IImageInfoDetector.cs
  9. 146
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  10. 47
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  11. 8
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  12. 46
      src/ImageSharp/Formats/Png/PngDecoder.cs
  13. 5
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  14. 47
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  15. 5
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  16. 10
      src/ImageSharp/Image.Decode.cs
  17. 6
      src/ImageSharp/Image.FromStream.cs
  18. 2
      src/ImageSharp/ImageSharp.csproj
  19. 2
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  20. 7
      tests/ImageSharp.Tests/TestFormat.cs
  21. 7
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  22. 10
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  23. 13
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

6
src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{
@ -32,5 +33,10 @@ namespace SixLabors.ImageSharp
: base(errorMessage, innerException)
{
}
internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException)
: this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException)
{
}
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -36,18 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot 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);
}
return decoder.Decode<TPixel>(stream, CreateLargeImageException);
}
/// <inheritdoc />
@ -55,46 +45,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new BmpDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
throw new InvalidImageContentException($"Cannot 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);
}
return decoder.DecodeAsync<TPixel>(stream, CreateLargeImageException, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(stream);
return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
return new BmpDecoderCore(configuration, this).Identify(stream, CreateLargeImageException);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(stream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new BmpDecoderCore(configuration, this).IdentifyAsync(stream, CreateLargeImageException, cancellationToken);
}
private static InvalidImageContentException CreateLargeImageException(InvalidMemoryOperationException ex, Size dims)
{
return new InvalidImageContentException($"Cannot 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);
}
}
}

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

@ -6,6 +6,7 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
try
@ -197,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -30,21 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.Decode<TPixel>(stream);
}
/// <inheritdoc />
@ -52,30 +39,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
GifThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.DecodeAsync<TPixel>(stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
@ -85,18 +59,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(bufferedStream, default);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public async Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
return await decoder.IdentifyAsync(bufferedStream, cancellationToken);
}
}
}

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

@ -6,6 +6,7 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator;
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
try
{

7
src/ImageSharp/Formats/IImageDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Formats
/// <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>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</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)
Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
@ -48,8 +50,9 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Task<Image> DecodeAsync(Configuration configuration, Stream stream);
Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

20
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
@ -17,21 +18,36 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
Configuration Configuration { get; }
/// <summary>
/// Gets the dimensions of the image being decoded.
/// </summary>
Size Dimensions { get; }
/// <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>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>The decoded image.</returns>
Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/>.</returns>
IImageInfo Identify(BufferedReadStream stream);
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
}
}

4
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace SixLabors.ImageSharp.Formats
@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats
/// </summary>
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="PixelTypeInfo"/> object</returns>
Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream);
Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken);
}
}

146
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -2,8 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
@ -14,21 +17,152 @@ namespace SixLabors.ImageSharp.Formats
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream)
=> Task.FromResult(decoder.Identify(stream));
public static Task<IImageInfo> IdentifyAsync(
this IImageDecoderInternals decoder,
Stream stream,
CancellationToken cancellationToken)
=> decoder.IdentifyAsync(stream, DefaultLargeImageExceptionFactory, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="tooLargeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<IImageInfo> IdentifyAsync(
this IImageDecoderInternals decoder,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> tooLargeImageExceptionFactory,
CancellationToken cancellationToken)
{
try
{
using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream);
IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken);
return Task.FromResult(imageInfo);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<IImageInfo>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<IImageInfo>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<IImageInfo>(ex);
}
}
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(this IImageDecoderInternals decoder, BufferedReadStream stream)
public static Task<Image<TPixel>> DecodeAsync<TPixel>(
this IImageDecoderInternals decoder,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> =>
decoder.DecodeAsync<TPixel>(
stream,
DefaultLargeImageExceptionFactory,
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="largeImageExceptionFactory">Factory method to handle <see cref="InvalidMemoryOperationException"/> as <see cref="InvalidImageContentException"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(
this IImageDecoderInternals decoder,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(decoder.Decode<TPixel>(stream));
{
try
{
using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream);
Image<TPixel> image = decoder.Decode<TPixel>(bufferedReadStream, cancellationToken);
return Task.FromResult(image);
}
catch (InvalidMemoryOperationException ex)
{
InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions);
return Task.FromException<Image<TPixel>>(invalidImageContentException);
}
catch (OperationCanceledException)
{
return Task.FromCanceled<Image<TPixel>>(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<Image<TPixel>>(ex);
}
}
public static IImageInfo Identify(this IImageDecoderInternals decoder, Stream stream)
=> decoder.Identify(stream, DefaultLargeImageExceptionFactory);
public static IImageInfo Identify(
this IImageDecoderInternals decoder,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory)
{
using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream);
try
{
return decoder.Identify(bufferedReadStream, default);
}
catch (InvalidMemoryOperationException ex)
{
throw largeImageExceptionFactory(ex, decoder.Dimensions);
}
}
public static Image<TPixel> Decode<TPixel>(this IImageDecoderInternals decoder, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(stream, DefaultLargeImageExceptionFactory);
public static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Stream stream,
Func<InvalidMemoryOperationException, Size, InvalidImageContentException> largeImageExceptionFactory)
where TPixel : unmanaged, IPixel<TPixel>
{
using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream);
try
{
return decoder.Decode<TPixel>(bufferedReadStream, default);
}
catch (InvalidMemoryOperationException ex)
{
throw largeImageExceptionFactory(ex, decoder.Dimensions);
}
}
private static InvalidImageContentException DefaultLargeImageExceptionFactory(
InvalidMemoryOperationException memoryOperationException,
Size dimensions) =>
new InvalidImageContentException(dimensions, memoryOperationException);
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -24,20 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.Decode<TPixel>(stream);
}
/// <inheritdoc />
@ -45,31 +33,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
(int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
JpegThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.DecodeAsync<TPixel>(stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
@ -77,20 +52,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
return decoder.IdentifyAsync(stream, cancellationToken);
}
}
}

8
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -117,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
@ -205,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ParseStream(stream);
@ -217,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ParseStream(stream, true);
this.InitExifProfile();

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -22,65 +23,36 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.Decode<TPixel>(stream);
}
/// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
PngThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.DecodeAsync<TPixel>(stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
var decoder = new PngDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Identify(bufferedStream);
return decoder.Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
var decoder = new PngDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(stream);
return decoder.IdentifyAsync(bufferedStream);
return decoder.IdentifyAsync(stream, cancellationToken);
}
}
}

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

@ -9,6 +9,7 @@ using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public Size Dimensions => new Size(this.header.Width, this.header.Height);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var metadata = new ImageMetadata();
@ -224,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata();

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -21,21 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return decoder.Decode<TPixel>(bufferedStream);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.Decode<TPixel>(stream);
}
/// <inheritdoc />
@ -43,49 +30,33 @@ namespace SixLabors.ImageSharp.Formats.Tga
=> this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public async Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TgaDecoderCore(configuration, this);
try
{
using var bufferedStream = new BufferedReadStream(stream);
return await decoder.DecodeAsync<TPixel>(bufferedStream).ConfigureAwait(false);
}
catch (InvalidMemoryOperationException ex)
{
Size dims = decoder.Dimensions;
TgaThrowHelper.ThrowInvalidImageContentException($"Cannot 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;
}
return decoder.DecodeAsync<TPixel>(stream, cancellationToken);
}
/// <inheritdoc />
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream)
=> await this.DecodeAsync<Rgba32>(configuration, stream).ConfigureAwait(false);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(stream);
return new TgaDecoderCore(configuration, this).Identify(bufferedStream);
return new TgaDecoderCore(configuration, this).Identify(stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
using var bufferedStream = new BufferedReadStream(stream);
return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
return new TgaDecoderCore(configuration, this).IdentifyAsync(stream, cancellationToken);
}
}
}

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

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream)
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
try
@ -640,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream)
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadFileHeader(stream);
return new ImageInfo(

10
src/ImageSharp/Image.Decode.cs

@ -163,13 +163,15 @@ namespace SixLabors.ImageSharp
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>(Stream stream, Configuration config, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false);
(IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config)
.ConfigureAwait(false);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream).ConfigureAwait(false);
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(config, stream, cancellationToken)
.ConfigureAwait(false);
return (img, format);
}
@ -193,7 +195,7 @@ namespace SixLabors.ImageSharp
return (null, null);
}
Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false);
Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false);
return (img, format);
}
@ -242,7 +244,7 @@ namespace SixLabors.ImageSharp
return (null, format);
}
IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false);
IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false);
return (info, format);
}
}

6
src/ImageSharp/Image.FromStream.cs

@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp
return WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync(configuration, s),
(s, ct) => decoder.DecodeAsync(configuration, s, ct),
cancellationToken);
}
@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s),
(s, ct) => decoder.DecodeAsync<TPixel>(Configuration.Default, s, ct),
cancellationToken);
/// <summary>
@ -577,7 +577,7 @@ namespace SixLabors.ImageSharp
=> WithSeekableStreamAsync(
configuration,
stream,
(s, ct) => decoder.DecodeAsync<TPixel>(configuration, s),
(s, ct) => decoder.DecodeAsync<TPixel>(configuration, s, ct),
cancellationToken);
/// <summary>

2
src/ImageSharp/ImageSharp.csproj

@ -22,7 +22,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" />
<PackageReference Include="MinVer" PrivateAssets="All" />
</ItemGroup>

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

@ -60,7 +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);
detector.Setup(x => x.IdentifyAsync(It.IsAny<Configuration>(), It.IsAny<Stream>(), TODO)).ReturnsAsync(this.localImageInfoMock.Object);
this.localDecoder = detector.As<IImageDecoder>();
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);

7
tests/ImageSharp.Tests/TestFormat.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
@ -219,7 +220,8 @@ namespace SixLabors.ImageSharp.Tests
return this.testFormat.Sample<TPixel>();
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration config, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration config, Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(config, stream));
@ -227,7 +229,8 @@ namespace SixLabors.ImageSharp.Tests
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 async Task<Image> DecodeAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken) => await this.DecodeAsync<TestPixelForAgnosticDecode>(configuration, stream, TODO);
}
public class TestEncoder : ImageSharp.Formats.IImageEncoder

7
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
@ -46,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
@ -82,6 +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);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken) => await this.DecodeAsync<Rgba32>(configuration, stream, TODO);
}
}

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Formats;
@ -14,7 +15,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.FromResult(this.Decode<TPixel>(configuration, stream));
@ -48,7 +50,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
}
}
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream)
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken)
=> Task.FromResult(this.Identify(configuration, stream));
public IImageInfo Identify(Configuration configuration, Stream stream)
@ -62,6 +65,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);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken) => await this.DecodeAsync<Rgba32>(configuration, stream, TODO);
}
}

13
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
@ -371,7 +372,8 @@ namespace SixLabors.ImageSharp.Tests
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
@ -391,7 +393,8 @@ namespace SixLabors.ImageSharp.Tests
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);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken) => await this.DecodeAsync<Rgba32>(configuration, stream, TODO);
}
private class TestDecoderWithParameters : IImageDecoder
@ -425,7 +428,8 @@ namespace SixLabors.ImageSharp.Tests
return new Image<TPixel>(42, 42);
}
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream)
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCountsAsync[this.callerName]++;
@ -445,7 +449,8 @@ namespace SixLabors.ImageSharp.Tests
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);
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream,
CancellationToken cancellationToken) => await this.DecodeAsync<Rgba32>(configuration, stream, TODO);
}
}
}

Loading…
Cancel
Save