Browse Source

Re-introduce IImageDecoder and split decoding pipelines.

pull/2301/head
James Jackson-South 4 years ago
parent
commit
1d994132ef
  1. 8
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 8
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 6
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 73
      src/ImageSharp/Formats/IImageDecoder.cs
  5. 23
      src/ImageSharp/Formats/IImageInfoDetector.cs
  6. 56
      src/ImageSharp/Formats/ISpecializedImageDecoder{T}.cs
  7. 178
      src/ImageSharp/Formats/ImageDecoder.cs
  8. 178
      src/ImageSharp/Formats/ImageDecoderExtensions.cs
  9. 16
      src/ImageSharp/Formats/ImageFormatManager.cs
  10. 8
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  11. 6
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  12. 6
      src/ImageSharp/Formats/Png/PngDecoder.cs
  13. 91
      src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
  14. 6
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  15. 3
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
  16. 6
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  17. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  18. 6
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  19. 1
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  20. 76
      src/ImageSharp/Image.Decode.cs
  21. 77
      src/ImageSharp/Image.FromStream.cs
  22. 5
      src/ImageSharp/Image.cs
  23. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  24. 10
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  25. 4
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  26. 10
      tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
  27. 12
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  28. 4
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  29. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
  30. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs
  31. 17
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  32. 6
      tests/ImageSharp.Tests/TestFile.cs
  33. 8
      tests/ImageSharp.Tests/TestFormat.cs
  34. 24
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  35. 12
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  36. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
  37. 12
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  38. 6
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  39. 4
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  40. 26
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  41. 8
      tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs
  42. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs
  43. 2
      tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs
  44. 4
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs
  45. 16
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

8
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -248,7 +248,7 @@ internal static class AotCompilerTools
} }
/// <summary> /// <summary>
/// This method pre-seeds the all <see cref="ImageDecoder"/> in the AoT compiler. /// This method pre-seeds the all <see cref="IImageDecoder"/> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve] [Preserve]
@ -280,15 +280,15 @@ internal static class AotCompilerTools
} }
/// <summary> /// <summary>
/// This method pre-seeds the <see cref="ImageDecoder"/> in the AoT compiler. /// This method pre-seeds the <see cref="IImageDecoder"/> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TDecoder">The decoder.</typeparam> /// <typeparam name="TDecoder">The decoder.</typeparam>
[Preserve] [Preserve]
private static void AotCompileImageDecoder<TPixel, TDecoder>() private static void AotCompileImageDecoder<TPixel, TDecoder>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : ImageDecoder where TDecoder : IImageDecoder
=> default(TDecoder).Decode<TPixel>(default, default, default); => default(TDecoder).Decode<TPixel>(default, default);
/// <summary> /// <summary>
/// This method pre-seeds the all <see cref="IImageProcessor" /> in the AoT compiler. /// This method pre-seeds the all <see cref="IImageProcessor" /> in the AoT compiler.

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp;
public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions> public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -33,10 +33,10 @@ public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
protected internal override BmpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) protected override BmpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options }; => new() { GeneralOptions = options };
} }

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
public sealed class GifDecoder : ImageDecoder public sealed class GifDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class GifDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -34,6 +34,6 @@ public sealed class GifDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
} }

73
src/ImageSharp/Formats/IImageDecoder.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Defines the contract for all image decoders.
/// </summary>
public interface IImageDecoder
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public IImageInfo Identify(DecoderOptions options, Stream stream);
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="options">The general decoder options.</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="Task{IImageInfo}"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<IImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <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="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image Decode(DecoderOptions options, 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="options">The general decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="options">The general decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
}

23
src/ImageSharp/Formats/IImageInfoDetector.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Encapsulates methods used for detecting the raw image information without fully decoding it.
/// </summary>
public interface IImageInfoDetector
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <param name="options">The general decoder options.</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="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
}

56
src/ImageSharp/Formats/ISpecializedImageDecoder{T}.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Defines the contract for an image decoder that supports specialized options.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
public interface ISpecializedImageDecoder<T> : IImageDecoder
where T : ISpecializedDecoderOptions
{
/// <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="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image<TPixel> Decode<TPixel>(T options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Image Decode(T options, 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="options">The specialized decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<Image<TPixel>> DecodeAsync<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="options">The specialized decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<Image> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken);
}

178
src/ImageSharp/Formats/ImageDecoder.cs

@ -1,15 +1,17 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
/// <summary> /// <summary>
/// The base class for all image decoders. /// Acts as a base class for image decoders.
/// Types that inherit this decoder are required to implement cancellable synchronous decoding operations only.
/// </summary> /// </summary>
public abstract class ImageDecoder public abstract class ImageDecoder : IImageDecoder
{ {
/// <summary> /// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type. /// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
@ -23,7 +25,7 @@ public abstract class ImageDecoder
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image{TPixel}" />.</returns> /// <returns>The <see cref="Image{TPixel}" />.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception> /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected internal abstract Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected abstract Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>
@ -37,7 +39,7 @@ public abstract class ImageDecoder
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image" />.</returns> /// <returns>The <see cref="Image" />.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception> /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected internal abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); protected abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
@ -50,7 +52,7 @@ public abstract class ImageDecoder
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns> /// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception> /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected internal abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); protected abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Performs a resize operation against the decoded image. If the target size is not set, or the image size /// Performs a resize operation against the decoded image. If the target size is not set, or the image size
@ -90,56 +92,132 @@ public abstract class ImageDecoder
Size currentSize = image.Size(); Size currentSize = image.Size();
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
} }
}
/// <summary> /// <inheritdoc/>
/// The base class for all specialized image decoders. public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream)
/// Specialized decoders allow for additional options to be passed to the decoder. where TPixel : unmanaged, IPixel<TPixel>
/// </summary> => WithSeekableStream(
/// <typeparam name="T">The type of specialized options.</typeparam> options,
public abstract class SpecializedImageDecoder<T> : ImageDecoder stream,
where T : ISpecializedDecoderOptions s => this.Decode<TPixel>(options, s, default));
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The specialized decoder options.</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>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected internal abstract Image<TPixel> Decode<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <inheritdoc/>
/// Decodes the image from the specified stream to an <see cref="Image" /> of a specific pixel type. public Image Decode(DecoderOptions options, Stream stream)
/// </summary> => WithSeekableStream(
/// <remarks> options,
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. stream,
/// </remarks> s => this.Decode(options, s, default));
/// <param name="options">The specialized decoder options.</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>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected internal abstract Image Decode(T options, Stream stream, CancellationToken cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) public Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TPixel>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); => WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
/// <summary> /// <inheritdoc/>
/// A factory method for creating the default specialized options. public IImageInfo Identify(DecoderOptions options, Stream stream)
/// </summary> => WithSeekableStream(
/// <param name="options">The general decoder options.</param> options,
/// <returns>The new <typeparamref name="T" />.</returns> stream,
protected internal abstract T CreateDefaultSpecializedOptions(DecoderOptions options); s => this.Identify(options, s, default));
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Identify(options, s, ct),
cancellationToken);
internal static T WithSeekableStream<T>(
DecoderOptions options,
Stream stream,
Func<Stream, T> action)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead)
{
throw new NotSupportedException("Cannot read from the stream.");
}
T Action(Stream s, long position)
{
T result = action(s);
// Our buffered reads may have left the stream in an incorrect non-zero position.
// Reset the position of the seekable stream if we did not read to the end to allow additional reads.
if (stream.CanSeek && stream.Position != s.Position && s.Position != s.Length)
{
stream.Position = position + s.Position;
}
return result;
}
if (stream.CanSeek)
{
return Action(stream, stream.Position);
}
Configuration configuration = options.Configuration;
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0;
return Action(memoryStream, 0);
}
internal static async Task<T> WithSeekableStreamAsync<T>(
DecoderOptions options,
Stream stream,
Func<Stream, CancellationToken, T> action,
CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead)
{
throw new NotSupportedException("Cannot read from the stream.");
}
T Action(Stream s, long position, CancellationToken ct)
{
T result = action(s, ct);
// Our buffered reads may have left the stream in an incorrect non-zero position.
// Reset the position of the seekable stream if we did not read to the end to allow additional reads.
if (stream.CanSeek && stream.Position != s.Position && s.Position != s.Length)
{
stream.Position = position + s.Position;
}
return result;
}
// NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that
// would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the
// code below to copy the stream to an in-memory buffer before invoking the action.
// TODO: Avoid the existing double copy caused by calling IdentifyAsync followed by DecodeAsync.
// Perhaps we can make overloads accepting the chunked memorystream?
Configuration configuration = options.Configuration;
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return Action(memoryStream, 0, cancellationToken);
}
} }

178
src/ImageSharp/Formats/ImageDecoderExtensions.cs

@ -1,178 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Extensions methods for <see cref="ImageDecoder"/> and <see cref="SpecializedImageDecoder{T}"/>.
/// </summary>
public static class ImageDecoderExtensions
{
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static IImageInfo Identify(this ImageDecoder decoder, DecoderOptions options, Stream stream)
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Identify(options, s, default));
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</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="Task{IImageInfo}"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<IImageInfo> IdentifyAsync(this ImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Identify(options, s, ct),
cancellationToken);
/// <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="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image<TPixel> Decode<TPixel>(this ImageDecoder decoder, DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Decode<TPixel>(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image Decode(this ImageDecoder decoder, DecoderOptions options, Stream stream)
=> Image.WithSeekableStream(
options,
stream,
s => decoder.Decode(options, s, default));
/// <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="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image<TPixel>> DecodeAsync<TPixel>(this ImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The general decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image> DecodeAsync(this ImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> Image.WithSeekableStreamAsync(
options,
stream,
(s, ct) => decoder.Decode(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image<TPixel> Decode<T, TPixel>(this SpecializedImageDecoder<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStream(
options.GeneralOptions,
stream,
s => decoder.Decode<TPixel>(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Image Decode<T>(this SpecializedImageDecoder<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
=> Image.WithSeekableStream(
options.GeneralOptions,
stream,
s => decoder.Decode(options, s, default));
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image<TPixel>> DecodeAsync<T, TPixel>(this SpecializedImageDecoder<T> decoder, T options, Stream stream, CancellationToken cancellationToken = default)
where T : ISpecializedDecoderOptions
where TPixel : unmanaged, IPixel<TPixel>
=> Image.WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => decoder.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/> of a specific pixel type.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
/// <param name="decoder">The decoder.</param>
/// <param name="options">The specialized decoder options.</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{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public static Task<Image> DecodeAsync<T>(this SpecializedImageDecoder<T> decoder, T options, Stream stream, CancellationToken cancellationToken = default)
where T : ISpecializedDecoderOptions
=> Image.WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => decoder.Decode(options, s, ct),
cancellationToken);
}

16
src/ImageSharp/Formats/ImageFormatManager.cs

@ -22,9 +22,9 @@ public class ImageFormatManager
private readonly ConcurrentDictionary<IImageFormat, ImageEncoder> mimeTypeEncoders = new(); private readonly ConcurrentDictionary<IImageFormat, ImageEncoder> mimeTypeEncoders = new();
/// <summary> /// <summary>
/// The list of supported <see cref="ImageDecoder"/> keyed to mime types. /// The list of supported <see cref="IImageDecoder"/> keyed to mime types.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<IImageFormat, ImageDecoder> mimeTypeDecoders = new(); private readonly ConcurrentDictionary<IImageFormat, IImageDecoder> mimeTypeDecoders = new();
/// <summary> /// <summary>
/// The list of supported <see cref="IImageFormat"/>s. /// The list of supported <see cref="IImageFormat"/>s.
@ -59,9 +59,9 @@ public class ImageFormatManager
internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors; internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors;
/// <summary> /// <summary>
/// Gets the currently registered <see cref="ImageDecoder"/>s. /// Gets the currently registered <see cref="IImageDecoder"/>s.
/// </summary> /// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, ImageDecoder>> ImageDecoders => this.mimeTypeDecoders; internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
/// <summary> /// <summary>
/// Gets the currently registered <see cref="ImageEncoder"/>s. /// Gets the currently registered <see cref="ImageEncoder"/>s.
@ -130,7 +130,7 @@ public class ImageFormatManager
/// </summary> /// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param> /// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="decoder">The decoder to use,</param> /// <param name="decoder">The decoder to use,</param>
public void SetDecoder(IImageFormat imageFormat, ImageDecoder decoder) public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder)
{ {
Guard.NotNull(imageFormat, nameof(imageFormat)); Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
@ -158,12 +158,12 @@ public class ImageFormatManager
/// For the specified mime type find the decoder. /// For the specified mime type find the decoder.
/// </summary> /// </summary>
/// <param name="format">The format to discover</param> /// <param name="format">The format to discover</param>
/// <returns>The <see cref="ImageDecoder"/> if found otherwise null</returns> /// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
public ImageDecoder FindDecoder(IImageFormat format) public IImageDecoder FindDecoder(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
return this.mimeTypeDecoders.TryGetValue(format, out ImageDecoder decoder) return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)
? decoder ? decoder
: null; : null;
} }

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions> public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -21,7 +21,7 @@ public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -38,10 +38,10 @@ public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(options, stream, cancellationToken); => this.Decode<Rgb24>(options, stream, cancellationToken);
/// <inheritdoc/> /// <inheritdoc/>
protected internal override JpegDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) protected override JpegDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options }; => new() { GeneralOptions = options };
} }

6
src/ImageSharp/Formats/Pbm/PbmDecoder.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
public sealed class PbmDecoder : ImageDecoder public sealed class PbmDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -36,7 +36,7 @@ public sealed class PbmDecoder : ImageDecoder
} }
/// <inheritdoc /> /// <inheritdoc />
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -50,6 +50,6 @@ public sealed class PbmDecoder : ImageDecoder
} }
/// <inheritdoc /> /// <inheritdoc />
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(options, stream, cancellationToken); => this.Decode<Rgb24>(options, stream, cancellationToken);
} }

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
public sealed class PngDecoder : ImageDecoder public sealed class PngDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class PngDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -34,7 +34,7 @@ public sealed class PngDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));

91
src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs

@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Acts as a base class for specialized image decoders.
/// Specialized decoders allow for additional options to be passed to the decoder.
/// Types that inherit this decoder are required to implement cancellable synchronous decoding operations only.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedImageDecoder<T>
where T : ISpecializedDecoderOptions
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The specialized decoder options.</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>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected abstract Image<TPixel> Decode<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image" /> of a specific pixel type.
/// </summary>
/// <remarks>
/// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use.
/// </remarks>
/// <param name="options">The specialized decoder options.</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>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected abstract Image Decode(T options, Stream stream, CancellationToken cancellationToken);
/// <inheritdoc/>
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TPixel>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
/// <inheritdoc/>
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
/// <summary>
/// A factory method for creating the default specialized options.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <returns>The new <typeparamref name="T" />.</returns>
protected abstract T CreateDefaultSpecializedOptions(DecoderOptions options);
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(T options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(
options.GeneralOptions,
stream,
s => this.Decode<TPixel>(options, s, default));
/// <inheritdoc/>
public Image Decode(T options, Stream stream)
=> WithSeekableStream(
options.GeneralOptions,
stream,
s => this.Decode(options, s, default));
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <inheritdoc/>
public Task<Image> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken)
=> WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
public sealed class TgaDecoder : ImageDecoder public sealed class TgaDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class TgaDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -34,6 +34,6 @@ public sealed class TgaDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
} }

3
src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs

@ -32,7 +32,8 @@ internal class WebpTiffCompression : TiffBaseDecompressor
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer, CancellationToken cancellationToken)
{ {
using Image<Rgb24> image = new WebpDecoder().Decode<Rgb24>(this.options, stream, cancellationToken); using WebpDecoderCore decoder = new(this.options);
using Image<Rgb24> image = decoder.Decode<Rgb24>(stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
} }

6
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
public class TiffDecoder : ImageDecoder public class TiffDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -20,7 +20,7 @@ public class TiffDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -34,6 +34,6 @@ public class TiffDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
} }

2
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -113,7 +113,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
public FaxCompressionOptions FaxCompressionOptions { get; set; } public FaxCompressionOptions FaxCompressionOptions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the the logical order of bits within a byte. /// Gets or sets the logical order of bits within a byte.
/// </summary> /// </summary>
public TiffFillOrder FillOrder { get; set; } public TiffFillOrder FillOrder { get; set; }

6
src/ImageSharp/Formats/Webp/WebpDecoder.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
public sealed class WebpDecoder : ImageDecoder public sealed class WebpDecoder : ImageDecoder
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -21,7 +21,7 @@ public sealed class WebpDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
@ -35,6 +35,6 @@ public sealed class WebpDecoder : ImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
} }

1
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -44,6 +44,7 @@ internal sealed class ChunkedMemoryStream : Stream
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class. /// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary> /// </summary>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(MemoryAllocator allocator) public ChunkedMemoryStream(MemoryAllocator allocator)
{ {
Guard.NotNull(allocator, nameof(allocator)); Guard.NotNull(allocator, nameof(allocator));

76
src/ImageSharp/Image.Decode.cs

@ -99,7 +99,7 @@ public abstract partial class Image
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The IImageFormat.</param> /// <param name="format">The IImageFormat.</param>
/// <returns>The image format or null if none found.</returns> /// <returns>The image format or null if none found.</returns>
private static ImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format) private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format)
{ {
format = InternalDetectFormat(options.Configuration, stream); format = InternalDetectFormat(options.Configuration, stream);
@ -113,36 +113,81 @@ public abstract partial class Image
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns> /// <returns>
/// A new <see cref="Image{TPixel}"/>. /// A new <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(options, stream);
return (img, format);
}
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
return (img, format);
}
private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream)
{
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return (null, null);
} }
Image<TPixel> img = decoder.Decode<TPixel>(options, stream, cancellationToken); Image img = decoder.Decode(options, stream);
return (img, format); return (img, format);
} }
private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken)
{ {
ImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return (null, null);
} }
Image img = decoder.Decode(options, stream, cancellationToken); Image img = await decoder.DecodeAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (img, format); return (img, format);
} }
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentify(DecoderOptions options, Stream stream)
{
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
IImageInfo info = decoder?.Identify(options, stream);
return (info, format);
}
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream. /// Reads the raw image information from the specified stream.
/// </summary> /// </summary>
@ -152,10 +197,19 @@ public abstract partial class Image
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns> /// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentifyAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken)
{ {
ImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
IImageInfo info = decoder?.Identify(options, stream, cancellationToken);
if (decoder is null)
{
return (null, null);
}
IImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (info, format); return (info, format);
} }
} }

77
src/ImageSharp/Image.FromStream.cs

@ -62,7 +62,7 @@ public abstract partial class Image
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
options, options,
stream, stream,
(s, _) => InternalDetectFormat(options.Configuration, s), (s, _) => Task.FromResult(InternalDetectFormat(options.Configuration, s)),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -160,7 +160,7 @@ public abstract partial class Image
/// </returns> /// </returns>
public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format) public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
{ {
(IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentity(options, s)); (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
format = data.Format; format = data.Format;
return data.ImageInfo; return data.ImageInfo;
@ -205,7 +205,7 @@ public abstract partial class Image
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
options, options,
stream, stream,
(s, ct) => InternalIdentity(options, s, ct), (s, ct) => InternalIdentifyAsync(options, s, ct),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -289,11 +289,7 @@ public abstract partial class Image
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) public static async Task<Image> LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{ => (await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false)).Image;
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false);
return fmt.Image;
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -416,7 +412,7 @@ public abstract partial class Image
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
(Image Image, IImageFormat Format) data = (Image Image, IImageFormat Format) data =
await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (data.Image is null) if (data.Image is null)
@ -447,7 +443,7 @@ public abstract partial class Image
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(Image<TPixel> Image, IImageFormat Format) data = (Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync(options, stream, (s, ct) => Decode<TPixel>(options, s, ct), cancellationToken) await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync<TPixel>(options, s, ct), cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (data.Image is null) if (data.Image is null)
@ -531,6 +527,20 @@ public abstract partial class Image
throw new NotSupportedException("Cannot read from the stream."); throw new NotSupportedException("Cannot read from the stream.");
} }
T Action(Stream s, long position)
{
T result = action(s);
// Our buffered reads may have left the stream in an incorrect non-zero position.
// Reset the position of the seekable stream if we did not read to the end to allow additional reads.
if (stream.CanSeek && stream.Position != s.Position && s.Position != s.Length)
{
stream.Position = position + s.Position;
}
return result;
}
Configuration configuration = options.Configuration; Configuration configuration = options.Configuration;
if (stream.CanSeek) if (stream.CanSeek)
{ {
@ -539,15 +549,14 @@ public abstract partial class Image
stream.Position = 0; stream.Position = 0;
} }
return action(stream); return Action(stream, stream.Position);
} }
// We want to be able to load images from things like HttpContext.Request.Body
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0; memoryStream.Position = 0;
return action(memoryStream); return Action(memoryStream, 0);
} }
/// <summary> /// <summary>
@ -563,7 +572,7 @@ public abstract partial class Image
internal static async Task<T> WithSeekableStreamAsync<T>( internal static async Task<T> WithSeekableStreamAsync<T>(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
Func<Stream, CancellationToken, T> action, Func<Stream, CancellationToken, Task<T>> action,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -574,34 +583,36 @@ public abstract partial class Image
throw new NotSupportedException("Cannot read from the stream."); throw new NotSupportedException("Cannot read from the stream.");
} }
Configuration configuration = options.Configuration; async Task<T> Action(Stream s, long position, CancellationToken ct)
if (stream.CanSeek && configuration.ReadOrigin == ReadOrigin.Begin)
{ {
stream.Position = 0; T result = await action(s, ct).ConfigureAwait(false);
// NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that // Our buffered reads may have left the stream in an incorrect non-zero position.
// would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the // Reset the position of the seekable stream if we did not read to the end to allow additional reads.
// code below to copy the stream to an in-memory buffer before invoking the action. if (stream.CanSeek && stream.Position != s.Position && s.Position != s.Length)
} {
stream.Position = position + s.Position;
}
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); return result;
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); }
memoryStream.Position = 0;
T Action(Stream ms, CancellationToken ct) Configuration configuration = options.Configuration;
if (stream.CanSeek)
{ {
// Reset the position of the seekable stream if we did not read to the end if (configuration.ReadOrigin == ReadOrigin.Begin)
// to allow additional reads.
T result = action(ms, ct);
if (stream.CanSeek && ms.Position != ms.Length)
{ {
stream.Position = ms.Position; stream.Position = 0;
} }
return result; return await Action(stream, stream.Position, cancellationToken).ConfigureAwait(false);
} }
return Action(memoryStream, cancellationToken); using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return await Action(memoryStream, 0, cancellationToken).ConfigureAwait(false);
} }
[DoesNotReturn] [DoesNotReturn]
@ -610,7 +621,7 @@ public abstract partial class Image
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine("Image cannot be loaded. Available decoders:"); sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, ImageDecoder> val in options.Configuration.ImageFormatsManager.ImageDecoders) foreach (KeyValuePair<IImageFormat, IImageDecoder> val in options.Configuration.ImageFormatsManager.ImageDecoders)
{ {
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
} }

5
src/ImageSharp/Image.cs

@ -41,6 +41,11 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="pixelType">The <see cref="PixelTypeInfo"/>.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
/// <param name="width">The width in px units.</param>
/// <param name="height">The height in px units.</param>
internal Image( internal Image(
Configuration configuration, Configuration configuration,
PixelTypeInfo pixelType, PixelTypeInfo pixelType,

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -26,6 +26,6 @@ public class IdentifyJpeg
{ {
using MemoryStream memoryStream = new(this.jpegBytes); using MemoryStream memoryStream = new(this.jpegBytes);
JpegDecoder decoder = new(); JpegDecoder decoder = new();
return decoder.Identify(DecoderOptions.Default, memoryStream, default); return decoder.Identify(DecoderOptions.Default, memoryStream);
} }
} }

10
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -278,9 +278,9 @@ public class BmpEncoderTests
// Use the default decoder to test our encoded image. This verifies the content. // Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid. // We do not verify the reference image though as some are invalid.
ImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile); using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default); using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream);
referenceImage.CompareToReferenceOutput( referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f), ImageComparer.TolerantPercentage(0.01f),
provider, provider,
@ -309,9 +309,9 @@ public class BmpEncoderTests
// Use the default decoder to test our encoded image. This verifies the content. // Use the default decoder to test our encoded image. This verifies the content.
// We do not verify the reference image though as some are invalid. // We do not verify the reference image though as some are invalid.
ImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile); using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default); using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream);
referenceImage.CompareToReferenceOutput( referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f), ImageComparer.TolerantPercentage(0.01f),
provider, provider,
@ -378,7 +378,7 @@ public class BmpEncoderTests
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null, IQuantizer quantizer = null,
ImageComparer customComparer = null, ImageComparer customComparer = null,
ImageDecoder referenceDecoder = null) IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();

4
tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs

@ -128,7 +128,7 @@ public class GifMetadataTests
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder(); var decoder = new GifDecoder();
IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream, default);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
@ -156,7 +156,7 @@ public class GifMetadataTests
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder(); var decoder = new GifDecoder();
using Image<Rgba32> image = await decoder.DecodeAsync<Rgba32>(DecoderOptions.Default, stream); using Image<Rgba32> image = await decoder.DecodeAsync<Rgba32>(DecoderOptions.Default, stream, default);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);

10
tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs

@ -64,7 +64,7 @@ public class ImageFormatManagerTests
[Fact] [Fact]
public void RegisterNullSetDecoder() public void RegisterNullSetDecoder()
{ {
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(null, new Mock<ImageDecoder>().Object)); Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(null, new Mock<IImageDecoder>().Object));
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null)); Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null));
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(null, null)); Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetDecoder(null, null));
} }
@ -87,14 +87,14 @@ public class ImageFormatManagerTests
[Fact] [Fact]
public void RegisterMimeTypeDecoderReplacesLast() public void RegisterMimeTypeDecoderReplacesLast()
{ {
ImageDecoder decoder1 = new Mock<ImageDecoder>().Object; IImageDecoder decoder1 = new Mock<IImageDecoder>().Object;
this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1);
ImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder1, found); Assert.Equal(decoder1, found);
ImageDecoder decoder2 = new Mock<ImageDecoder>().Object; IImageDecoder decoder2 = new Mock<IImageDecoder>().Object;
this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2);
ImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder2, found2); Assert.Equal(decoder2, found2);
Assert.NotEqual(found, found2); Assert.NotEqual(found, found2);
} }

12
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -106,7 +106,7 @@ public partial class JpegDecoderTests
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder(); var decoder = new JpegDecoder();
IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream, default);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
@ -142,7 +142,7 @@ public partial class JpegDecoderTests
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using var stream = new MemoryStream(testFile.Bytes, false);
using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream); using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream, default);
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality); Assert.Equal(quality, meta.Quality);
} }
@ -178,25 +178,25 @@ public partial class JpegDecoderTests
Assert.Equal(expectedColorType, meta.ColorType); Assert.Equal(expectedColorType, meta.ColorType);
} }
private static void TestImageInfo(string imagePath, ImageDecoder decoder, bool useIdentify, Action<IImageInfo> test) private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using var stream = new MemoryStream(testFile.Bytes, false);
if (useIdentify) if (useIdentify)
{ {
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default); IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo); test(imageInfo);
} }
else else
{ {
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default); using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
test(img); test(img);
} }
} }
private static void TestMetadataImpl( private static void TestMetadataImpl(
bool useIdentify, bool useIdentify,
ImageDecoder decoder, IImageDecoder decoder,
string imagePath, string imagePath,
int expectedPixelSize, int expectedPixelSize,
bool exifProfilePresent, bool exifProfilePresent,

4
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -588,7 +588,7 @@ public partial class PngEncoderTests
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType);
// Compare to the Magick reference decoder. // Compare to the Magick reference decoder.
ImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
// We compare using both our decoder and the reference decoder as pixel transformation // We compare using both our decoder and the reference decoder as pixel transformation
// occurs within the encoder itself leaving the input image unaffected. // occurs within the encoder itself leaving the input image unaffected.
@ -598,7 +598,7 @@ public partial class PngEncoderTests
fileStream.Position = 0; fileStream.Position = 0;
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, fileStream, default); using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, fileStream);
ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage);
} }
} }

2
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs

@ -16,7 +16,7 @@ public abstract class TiffDecoderBaseTester
protected static MagickReferenceDecoder ReferenceDecoder => new(); protected static MagickReferenceDecoder ReferenceDecoder => new();
protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, ImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> image = provider.GetImage(TiffDecoder); using Image<TPixel> image = provider.GetImage(TiffDecoder);

4
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff;
[Trait("Format", "Tiff")] [Trait("Format", "Tiff")]
public abstract class TiffEncoderBaseTester public abstract class TiffEncoderBaseTester
{ {
protected static readonly ImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
protected static void TestStripLength<TPixel>( protected static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
@ -85,7 +85,7 @@ public abstract class TiffEncoderBaseTester
TiffPredictor predictor = TiffPredictor.None, TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true, bool useExactComparer = true,
float compareTolerance = 0.001f, float compareTolerance = 0.001f,
ImageDecoder imageDecoder = null) IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();

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

@ -19,7 +19,7 @@ public partial class ImageTests
protected Image<Bgra4444> localStreamReturnImageAgnostic; protected Image<Bgra4444> localStreamReturnImageAgnostic;
protected Mock<ImageDecoder> localDecoder; protected Mock<IImageDecoder> localDecoder;
protected IImageFormatDetector localMimeTypeDetector; protected IImageFormatDetector localMimeTypeDetector;
@ -59,13 +59,16 @@ public partial class ImageTests
this.localImageInfoMock = new Mock<IImageInfo>(); this.localImageInfoMock = new Mock<IImageInfo>();
this.localImageFormatMock = new Mock<IImageFormat>(); this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<ImageDecoder>(); this.localDecoder = new Mock<IImageDecoder>();
this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Returns(this.localImageInfoMock.Object); .Returns(this.localImageInfoMock.Object);
this.localDecoder.Setup(x => x.IdentifyAsync(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(this.localImageInfoMock.Object));
this.localDecoder this.localDecoder
.Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) .Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) => .Callback<DecoderOptions, Stream>((c, s) =>
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
s.CopyTo(ms); s.CopyTo(ms);
@ -74,8 +77,8 @@ public partial class ImageTests
.Returns(this.localStreamReturnImageRgba32); .Returns(this.localStreamReturnImageRgba32);
this.localDecoder this.localDecoder
.Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) .Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) => .Callback<DecoderOptions, Stream>((c, s) =>
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
s.CopyTo(ms); s.CopyTo(ms);

6
tests/ImageSharp.Tests/TestFile.cs

@ -147,7 +147,7 @@ public sealed class TestFile
/// <returns> /// <returns>
/// The <see cref="Image{Rgba32}"/>. /// The <see cref="Image{Rgba32}"/>.
/// </returns> /// </returns>
public Image<Rgba32> CreateRgba32Image(ImageDecoder decoder) public Image<Rgba32> CreateRgba32Image(IImageDecoder decoder)
=> this.CreateRgba32Image(decoder, new()); => this.CreateRgba32Image(decoder, new());
/// <summary> /// <summary>
@ -158,10 +158,10 @@ public sealed class TestFile
/// <returns> /// <returns>
/// The <see cref="Image{Rgba32}"/>. /// The <see cref="Image{Rgba32}"/>.
/// </returns> /// </returns>
public Image<Rgba32> CreateRgba32Image(ImageDecoder decoder, DecoderOptions options) public Image<Rgba32> CreateRgba32Image(IImageDecoder decoder, DecoderOptions options)
{ {
options.Configuration = this.Image.GetConfiguration(); options.Configuration = this.Image.GetConfiguration();
using MemoryStream stream = new(this.Bytes); using MemoryStream stream = new(this.Bytes);
return decoder.Decode<Rgba32>(options, stream, default); return decoder.Decode<Rgba32>(options, stream);
} }
} }

8
tests/ImageSharp.Tests/TestFormat.cs

@ -201,13 +201,13 @@ public class TestFormat : IConfigurationModule, IImageFormat
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header); public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); => this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected internal override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options }; => new() { GeneralOptions = options };
protected internal override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Configuration configuration = options.GeneralOptions.Configuration; Configuration configuration = options.GeneralOptions.Configuration;
var ms = new MemoryStream(); var ms = new MemoryStream();
@ -224,7 +224,7 @@ public class TestFormat : IConfigurationModule, IImageFormat
return this.testFormat.Sample<TPixel>(); return this.testFormat.Sample<TPixel>();
} }
protected internal override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TestPixelForAgnosticDecode>(options, stream, cancellationToken); => this.Decode<TestPixelForAgnosticDecode>(options, stream, cancellationToken);
} }

24
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -27,7 +27,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
public Key( public Key(
PixelTypes pixelType, PixelTypes pixelType,
string filePath, string filePath,
ImageDecoder customDecoder, IImageDecoder customDecoder,
DecoderOptions options, DecoderOptions options,
ISpecializedDecoderOptions specialized) ISpecializedDecoderOptions specialized)
{ {
@ -175,11 +175,11 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
public override Image<TPixel> GetImage() public override Image<TPixel> GetImage()
{ {
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath);
return this.GetImage(decoder); return this.GetImage(decoder);
} }
public override Image<TPixel> GetImage(ImageDecoder decoder, DecoderOptions options) public override Image<TPixel> GetImage(IImageDecoder decoder, DecoderOptions options)
{ {
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -202,7 +202,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
return cachedImage.Clone(this.Configuration); return cachedImage.Clone(this.Configuration);
} }
public override Task<Image<TPixel>> GetImageAsync(ImageDecoder decoder, DecoderOptions options) public override async Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder, DecoderOptions options)
{ {
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -213,10 +213,10 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
// TODO: Check Path here. Why combined? // TODO: Check Path here. Why combined?
string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath);
using Stream stream = System.IO.File.OpenRead(path); using Stream stream = System.IO.File.OpenRead(path);
return Task.FromResult(decoder.Decode<TPixel>(options, stream, default)); return await decoder.DecodeAsync<TPixel>(options, stream, default);
} }
public override Image<TPixel> GetImage<T>(SpecializedImageDecoder<T> decoder, T options) public override Image<TPixel> GetImage<T>(ISpecializedImageDecoder<T> decoder, T options)
{ {
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -239,7 +239,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
return cachedImage.Clone(this.Configuration); return cachedImage.Clone(this.Configuration);
} }
public override Task<Image<TPixel>> GetImageAsync<T>(SpecializedImageDecoder<T> decoder, T options) public override async Task<Image<TPixel>> GetImageAsync<T>(ISpecializedImageDecoder<T> decoder, T options)
{ {
Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(decoder, nameof(decoder));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -250,7 +250,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
// TODO: Check Path here. Why combined? // TODO: Check Path here. Why combined?
string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath);
using Stream stream = System.IO.File.OpenRead(path); using Stream stream = System.IO.File.OpenRead(path);
return Task.FromResult(decoder.Decode<TPixel>(options, stream, default)); return await decoder.DecodeAsync<TPixel>(options, stream, default);
} }
public override void Deserialize(IXunitSerializationInfo info) public override void Deserialize(IXunitSerializationInfo info)
@ -266,23 +266,23 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
info.AddValue("path", this.FilePath); info.AddValue("path", this.FilePath);
} }
private Image<TPixel> DecodeImage(ImageDecoder decoder, DecoderOptions options) private Image<TPixel> DecodeImage(IImageDecoder decoder, DecoderOptions options)
{ {
options.Configuration = this.Configuration; options.Configuration = this.Configuration;
var testFile = TestFile.Create(this.FilePath); var testFile = TestFile.Create(this.FilePath);
using Stream stream = new MemoryStream(testFile.Bytes); using Stream stream = new MemoryStream(testFile.Bytes);
return decoder.Decode<TPixel>(options, stream, default); return decoder.Decode<TPixel>(options, stream);
} }
private Image<TPixel> DecodeImage<T>(SpecializedImageDecoder<T> decoder, T options) private Image<TPixel> DecodeImage<T>(ISpecializedImageDecoder<T> decoder, T options)
where T : class, ISpecializedDecoderOptions, new() where T : class, ISpecializedDecoderOptions, new()
{ {
options.GeneralOptions.Configuration = this.Configuration; options.GeneralOptions.Configuration = this.Configuration;
var testFile = TestFile.Create(this.FilePath); var testFile = TestFile.Create(this.FilePath);
using Stream stream = new MemoryStream(testFile.Bytes); using Stream stream = new MemoryStream(testFile.Bytes);
return decoder.Decode<TPixel>(options, stream, default); return decoder.Decode<TPixel>(options, stream);
} }
} }

12
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -87,23 +87,23 @@ public abstract partial class TestImageProvider<TPixel> : ITestImageProvider, IX
/// <returns>A test image.</returns> /// <returns>A test image.</returns>
public abstract Image<TPixel> GetImage(); public abstract Image<TPixel> GetImage();
public Image<TPixel> GetImage(ImageDecoder decoder) public Image<TPixel> GetImage(IImageDecoder decoder)
=> this.GetImage(decoder, new()); => this.GetImage(decoder, new());
public Task<Image<TPixel>> GetImageAsync(ImageDecoder decoder) public Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder)
=> this.GetImageAsync(decoder, new()); => this.GetImageAsync(decoder, new());
public virtual Image<TPixel> GetImage(ImageDecoder decoder, DecoderOptions options) public virtual Image<TPixel> GetImage(IImageDecoder decoder, DecoderOptions options)
=> throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!");
public virtual Task<Image<TPixel>> GetImageAsync(ImageDecoder decoder, DecoderOptions options) public virtual Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder, DecoderOptions options)
=> throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!");
public virtual Image<TPixel> GetImage<T>(SpecializedImageDecoder<T> decoder, T options) public virtual Image<TPixel> GetImage<T>(ISpecializedImageDecoder<T> decoder, T options)
where T : class, ISpecializedDecoderOptions, new() where T : class, ISpecializedDecoderOptions, new()
=> throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!");
public virtual Task<Image<TPixel>> GetImageAsync<T>(SpecializedImageDecoder<T> decoder, T options) public virtual Task<Image<TPixel>> GetImageAsync<T>(ISpecializedImageDecoder<T> decoder, T options)
where T : class, ISpecializedDecoderOptions, new() where T : class, ISpecializedDecoderOptions, new()
=> throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!");

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs

@ -45,6 +45,6 @@ public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder
// IDisposable means you must use async/await, where the compiler generates the // IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation. // state machine and a continuation.
using PngEncoderCore encoder = new(allocator, configuration, this); using PngEncoderCore encoder = new(allocator, configuration, this);
await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); await encoder.EncodeAsync(image, stream, cancellationToken);
} }
} }

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

@ -24,7 +24,7 @@ public class MagickReferenceDecoder : ImageDecoder
public static MagickReferenceDecoder Instance { get; } = new(); public static MagickReferenceDecoder Instance { get; } = new();
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
Configuration configuration = options.Configuration; Configuration configuration = options.Configuration;
BmpReadDefines bmpReadDefines = new() BmpReadDefines bmpReadDefines = new()
@ -32,8 +32,10 @@ public class MagickReferenceDecoder : ImageDecoder
IgnoreFileSize = !this.validate IgnoreFileSize = !this.validate
}; };
MagickReadSettings settings = new(); MagickReadSettings settings = new()
settings.FrameCount = (int)options.MaxFrames; {
FrameCount = (int)options.MaxFrames
};
settings.SetDefines(bmpReadDefines); settings.SetDefines(bmpReadDefines);
using MagickImageCollection magickImageCollection = new(stream, settings); using MagickImageCollection magickImageCollection = new(stream, settings);
@ -67,10 +69,10 @@ public class MagickReferenceDecoder : ImageDecoder
return new Image<TPixel>(configuration, new ImageMetadata(), framesList); return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
} }
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
private static void FromRgba32Bytes<TPixel>(Configuration configuration, Span<byte> rgbaBytes, IMemoryGroup<TPixel> destinationGroup) private static void FromRgba32Bytes<TPixel>(Configuration configuration, Span<byte> rgbaBytes, IMemoryGroup<TPixel> destinationGroup)

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

@ -13,14 +13,14 @@ public class SystemDrawingReferenceDecoder : ImageDecoder
{ {
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
using SDBitmap sourceBitmap = new(stream); using SDBitmap sourceBitmap = new(stream);
PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));
return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata());
} }
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
using SDBitmap sourceBitmap = new(stream); using SDBitmap sourceBitmap = new(stream);
if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
@ -45,6 +45,6 @@ public class SystemDrawingReferenceDecoder : ImageDecoder
return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap<TPixel>(convertedBitmap); return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap<TPixel>(convertedBitmap);
} }
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
} }

4
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

@ -20,7 +20,7 @@ public static partial class TestEnvironment
internal static Configuration Configuration => ConfigurationLazy.Value; internal static Configuration Configuration => ConfigurationLazy.Value;
internal static ImageDecoder GetReferenceDecoder(string filePath) internal static IImageDecoder GetReferenceDecoder(string filePath)
{ {
IImageFormat format = GetImageFormat(filePath); IImageFormat format = GetImageFormat(filePath);
return Configuration.ImageFormatsManager.FindDecoder(format); return Configuration.ImageFormatsManager.FindDecoder(format);
@ -42,7 +42,7 @@ public static partial class TestEnvironment
private static void ConfigureCodecs( private static void ConfigureCodecs(
this Configuration cfg, this Configuration cfg,
IImageFormat imageFormat, IImageFormat imageFormat,
ImageDecoder decoder, IImageDecoder decoder,
ImageEncoder encoder, ImageEncoder encoder,
IImageFormatDetector detector) IImageFormatDetector detector)
{ {

26
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -214,7 +214,7 @@ public static class TestImageExtensions
bool grayscale = false, bool grayscale = false,
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
ImageDecoder decoder = null) IImageDecoder decoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> referenceImage = GetReferenceOutputImage<TPixel>( using (Image<TPixel> referenceImage = GetReferenceOutputImage<TPixel>(
@ -307,7 +307,7 @@ public static class TestImageExtensions
string extension = "png", string extension = "png",
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
ImageDecoder decoder = null) IImageDecoder decoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(
@ -324,7 +324,7 @@ public static class TestImageExtensions
decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile);
using FileStream stream = File.OpenRead(referenceOutputFile); using FileStream stream = File.OpenRead(referenceOutputFile);
return decoder.Decode<TPixel>(DecoderOptions.Default, stream, default); return decoder.Decode<TPixel>(DecoderOptions.Default, stream);
} }
public static Image<TPixel> GetReferenceOutputImageMultiFrame<TPixel>( public static Image<TPixel> GetReferenceOutputImageMultiFrame<TPixel>(
@ -343,17 +343,17 @@ public static class TestImageExtensions
var temporaryFrameImages = new List<Image<TPixel>>(); var temporaryFrameImages = new List<Image<TPixel>>();
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]);
foreach (string path in frameFiles) foreach (string path in frameFiles)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
throw new Exception("Reference output file missing: " + path); throw new FileNotFoundException("Reference output file missing: " + path);
} }
using FileStream stream = File.OpenRead(path); using FileStream stream = File.OpenRead(path);
Image<TPixel> tempImage = decoder.Decode<TPixel>(DecoderOptions.Default, stream, default); Image<TPixel> tempImage = decoder.Decode<TPixel>(DecoderOptions.Default, stream);
temporaryFrameImages.Add(tempImage); temporaryFrameImages.Add(tempImage);
} }
@ -511,7 +511,7 @@ public static class TestImageExtensions
public static Image<TPixel> CompareToOriginal<TPixel>( public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
ITestImageProvider provider, ITestImageProvider provider,
ImageDecoder referenceDecoder = null) IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); => CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder);
@ -519,7 +519,7 @@ public static class TestImageExtensions
this Image<TPixel> image, this Image<TPixel> image,
ITestImageProvider provider, ITestImageProvider provider,
ImageComparer comparer, ImageComparer comparer,
ImageDecoder referenceDecoder = null, IImageDecoder referenceDecoder = null,
DecoderOptions referenceDecoderOptions = null) DecoderOptions referenceDecoderOptions = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -534,7 +534,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path);
using MemoryStream stream = new(testFile.Bytes); using MemoryStream stream = new(testFile.Bytes);
using (Image<TPixel> original = referenceDecoder.Decode<TPixel>(referenceDecoderOptions ?? DecoderOptions.Default, stream, default)) using (Image<TPixel> original = referenceDecoder.Decode<TPixel>(referenceDecoderOptions ?? DecoderOptions.Default, stream))
{ {
comparer.VerifySimilarity(original, image); comparer.VerifySimilarity(original, image);
} }
@ -546,7 +546,7 @@ public static class TestImageExtensions
this Image<TPixel> image, this Image<TPixel> image,
ITestImageProvider provider, ITestImageProvider provider,
ImageComparer comparer, ImageComparer comparer,
ImageDecoder referenceDecoder = null) IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider); string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
@ -560,7 +560,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path);
using MemoryStream stream = new(testFile.Bytes); using MemoryStream stream = new(testFile.Bytes);
using (Image<TPixel> original = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default)) using (Image<TPixel> original = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream))
{ {
comparer.VerifySimilarity(original, image); comparer.VerifySimilarity(original, image);
} }
@ -668,7 +668,7 @@ public static class TestImageExtensions
ImageComparer customComparer = null, ImageComparer customComparer = null,
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
string referenceImageExtension = null, string referenceImageExtension = null,
ImageDecoder referenceDecoder = null) IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
string actualOutputFile = provider.Utility.SaveTestOutputFile( string actualOutputFile = provider.Utility.SaveTestOutputFile(
@ -681,7 +681,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile);
using FileStream stream = File.OpenRead(actualOutputFile); using FileStream stream = File.OpenRead(actualOutputFile);
using Image<TPixel> encodedImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default); using Image<TPixel> encodedImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream);
ImageComparer comparer = customComparer ?? ImageComparer.Exact; ImageComparer comparer = customComparer ?? ImageComparer.Exact;
comparer.VerifySimilarity(encodedImage, image); comparer.VerifySimilarity(encodedImage, image);

8
tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs

@ -40,8 +40,8 @@ public class MagickReferenceCodecTests
using FileStream mStream = File.OpenRead(path); using FileStream mStream = File.OpenRead(path);
using FileStream sdStream = File.OpenRead(path); using FileStream sdStream = File.OpenRead(path);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream, default); using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream, default); using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream);
ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage); ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage);
@ -71,8 +71,8 @@ public class MagickReferenceCodecTests
var comparer = ImageComparer.TolerantPercentage(1, 1020); var comparer = ImageComparer.TolerantPercentage(1, 1020);
using FileStream mStream = File.OpenRead(path); using FileStream mStream = File.OpenRead(path);
using FileStream sdStream = File.OpenRead(path); using FileStream sdStream = File.OpenRead(path);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream, default); using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream, default); using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream);
ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage); ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage);
mImage.DebugSave(dummyProvider); mImage.DebugSave(dummyProvider);

2
tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs

@ -74,7 +74,7 @@ public class ReferenceDecoderBenchmarks
this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp");
} }
private void BenchmarkDecoderImpl(IEnumerable<string> testFiles, ImageDecoder decoder, string info, int times = DefaultExecutionCount) private void BenchmarkDecoderImpl(IEnumerable<string> testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount)
{ {
var measure = new MeasureFixture(this.Output); var measure = new MeasureFixture(this.Output);
measure.Measure( measure.Measure(

2
tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs

@ -93,7 +93,7 @@ public class SystemDrawingReferenceCodecTests
{ {
string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash);
using FileStream stream = File.OpenRead(path); using FileStream stream = File.OpenRead(path);
using Image<TPixel> image = SystemDrawingReferenceDecoder.Instance.Decode<TPixel>(DecoderOptions.Default, stream, default); using Image<TPixel> image = SystemDrawingReferenceDecoder.Instance.Decode<TPixel>(DecoderOptions.Default, stream);
image.DebugSave(dummyProvider); image.DebugSave(dummyProvider);
} }

4
tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

@ -79,7 +79,7 @@ public class TestEnvironmentTests
return; return;
} }
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
Assert.IsType(expectedDecoderType, decoder); Assert.IsType(expectedDecoderType, decoder);
} }
@ -113,7 +113,7 @@ public class TestEnvironmentTests
return; return;
} }
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
Assert.IsType(expectedDecoderType, decoder); Assert.IsType(expectedDecoderType, decoder);
} }

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

@ -363,19 +363,19 @@ public class TestImageProviderTests
} }
} }
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); => this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected internal override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{ {
InvocationCounts[this.callerName]++; InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42); return new Image<TPixel>(42, 42);
} }
protected internal override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
protected internal override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options }; => new() { GeneralOptions = options };
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
@ -403,19 +403,19 @@ public class TestImageProviderTests
} }
} }
protected internal override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); => this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected internal override Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) protected override Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
{ {
InvocationCounts[this.callerName]++; InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42); return new Image<TPixel>(42, 42);
} }
protected internal override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) protected override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken); => this.Decode<Rgba32>(options, stream, cancellationToken);
protected internal override TestDecoderWithParametersOptions CreateDefaultSpecializedOptions(DecoderOptions options) protected override TestDecoderWithParametersOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options }; => new() { GeneralOptions = options };
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];

Loading…
Cancel
Save