Browse Source

Re-introduce IImageDecoder and split decoding pipelines.

pull/2301/head
James Jackson-South 3 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>
/// 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
@ -280,15 +280,15 @@ internal static class AotCompilerTools
}
/// <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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TDecoder">The decoder.</typeparam>
[Preserve]
private static void AotCompileImageDecoder<TPixel, TDecoder>()
where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : ImageDecoder
=> default(TDecoder).Decode<TPixel>(default, default, default);
where TDecoder : IImageDecoder
=> default(TDecoder).Decode<TPixel>(default, default);
/// <summary>
/// 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>
{
/// <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(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
}
/// <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(stream, nameof(stream));
@ -33,10 +33,10 @@ public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
}
/// <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);
/// <inheritdoc/>
protected internal override BmpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
protected override BmpDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions 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
{
/// <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(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class GifDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -34,6 +34,6 @@ public sealed class GifDecoder : ImageDecoder
}
/// <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);
}

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.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats;
/// <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>
public abstract class ImageDecoder
public abstract class ImageDecoder : IImageDecoder
{
/// <summary>
/// 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>
/// <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>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected abstract Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
@ -37,7 +39,7 @@ public abstract class ImageDecoder
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Image" />.</returns>
/// <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>
/// 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>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <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>
/// 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();
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
}
}
/// <summary>
/// The base class for all specialized image decoders.
/// Specialized decoders allow for additional options to be passed to the decoder.
/// </summary>
/// <typeparam name="T">The type of specialized options.</typeparam>
public abstract class SpecializedImageDecoder<T> : ImageDecoder
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 internal abstract Image<TPixel> Decode<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(
options,
stream,
s => this.Decode<TPixel>(options, s, default));
/// <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 internal abstract Image Decode(T options, Stream stream, CancellationToken cancellationToken);
/// <inheritdoc/>
public Image Decode(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
s => this.Decode(options, s, default));
/// <inheritdoc/>
protected internal override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TPixel>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
public Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken);
/// <inheritdoc/>
protected internal override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode(options, s, ct),
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 internal abstract T CreateDefaultSpecializedOptions(DecoderOptions options);
/// <inheritdoc/>
public IImageInfo Identify(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
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();
/// <summary>
/// The list of supported <see cref="ImageDecoder"/> keyed to mime types.
/// The list of supported <see cref="IImageDecoder"/> keyed to mime types.
/// </summary>
private readonly ConcurrentDictionary<IImageFormat, ImageDecoder> mimeTypeDecoders = new();
private readonly ConcurrentDictionary<IImageFormat, IImageDecoder> mimeTypeDecoders = new();
/// <summary>
/// The list of supported <see cref="IImageFormat"/>s.
@ -59,9 +59,9 @@ public class ImageFormatManager
internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors;
/// <summary>
/// Gets the currently registered <see cref="ImageDecoder"/>s.
/// Gets the currently registered <see cref="IImageDecoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, ImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
/// <summary>
/// Gets the currently registered <see cref="ImageEncoder"/>s.
@ -130,7 +130,7 @@ public class ImageFormatManager
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</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(decoder, nameof(decoder));
@ -158,12 +158,12 @@ public class ImageFormatManager
/// For the specified mime type find the decoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="ImageDecoder"/> if found otherwise null</returns>
public ImageDecoder FindDecoder(IImageFormat format)
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
public IImageDecoder FindDecoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
return this.mimeTypeDecoders.TryGetValue(format, out ImageDecoder decoder)
return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)
? decoder
: null;
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
{
/// <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(stream, nameof(stream));
@ -21,7 +21,7 @@ public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
}
/// <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(stream, nameof(stream));
@ -38,10 +38,10 @@ public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
}
/// <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);
/// <inheritdoc/>
protected internal override JpegDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
protected override JpegDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions 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
{
/// <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(stream, nameof(stream));
@ -36,7 +36,7 @@ public sealed class PbmDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -50,6 +50,6 @@ public sealed class PbmDecoder : ImageDecoder
}
/// <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);
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
public sealed class PngDecoder : ImageDecoder
{
/// <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(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class PngDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -34,7 +34,7 @@ public sealed class PngDecoder : ImageDecoder
}
/// <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(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
{
/// <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(stream, nameof(stream));
@ -20,7 +20,7 @@ public sealed class TgaDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -34,6 +34,6 @@ public sealed class TgaDecoder : ImageDecoder
}
/// <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);
}

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

@ -32,7 +32,8 @@ internal class WebpTiffCompression : TiffBaseDecompressor
/// <inheritdoc/>
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);
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff;
public class TiffDecoder : ImageDecoder
{
/// <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(stream, nameof(stream));
@ -20,7 +20,7 @@ public class TiffDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -34,6 +34,6 @@ public class TiffDecoder : ImageDecoder
}
/// <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);
}

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

@ -113,7 +113,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
public FaxCompressionOptions FaxCompressionOptions { get; set; }
/// <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>
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
{
/// <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(stream, nameof(stream));
@ -21,7 +21,7 @@ public sealed class WebpDecoder : ImageDecoder
}
/// <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(stream, nameof(stream));
@ -35,6 +35,6 @@ public sealed class WebpDecoder : ImageDecoder
}
/// <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);
}

1
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -44,6 +44,7 @@ internal sealed class ChunkedMemoryStream : Stream
/// <summary>
/// Initializes a new instance of the <see cref="ChunkedMemoryStream"/> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(MemoryAllocator 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="format">The IImageFormat.</param>
/// <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);
@ -113,36 +113,81 @@ public abstract partial class Image
/// </summary>
/// <param name="options">The general decoder options.</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>
/// <returns>
/// A new <see cref="Image{TPixel}"/>.
/// </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>
{
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)
{
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(options, stream, cancellationToken);
Image img = decoder.Decode(options, stream);
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)
{
return (null, null);
}
Image img = decoder.Decode(options, stream, cancellationToken);
Image img = await decoder.DecodeAsync(options, stream, cancellationToken).ConfigureAwait(false);
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>
/// Reads the raw image information from the specified stream.
/// </summary>
@ -152,10 +197,19 @@ public abstract partial class Image
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// </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);
IImageInfo info = decoder?.Identify(options, stream, cancellationToken);
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
if (decoder is null)
{
return (null, null);
}
IImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (info, format);
}
}

77
src/ImageSharp/Image.FromStream.cs

@ -62,7 +62,7 @@ public abstract partial class Image
=> WithSeekableStreamAsync(
options,
stream,
(s, _) => InternalDetectFormat(options.Configuration, s),
(s, _) => Task.FromResult(InternalDetectFormat(options.Configuration, s)),
cancellationToken);
/// <summary>
@ -160,7 +160,7 @@ public abstract partial class Image
/// </returns>
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;
return data.ImageInfo;
@ -205,7 +205,7 @@ public abstract partial class Image
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => InternalIdentity(options, s, ct),
(s, ct) => InternalIdentifyAsync(options, s, ct),
cancellationToken);
/// <summary>
@ -289,11 +289,7 @@ public abstract partial class Image
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{
(Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken)
.ConfigureAwait(false);
return fmt.Image;
}
=> (await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false)).Image;
/// <summary>
/// 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)
{
(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);
if (data.Image is null)
@ -447,7 +443,7 @@ public abstract partial class Image
where TPixel : unmanaged, IPixel<TPixel>
{
(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);
if (data.Image is null)
@ -531,6 +527,20 @@ public abstract partial class Image
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;
if (stream.CanSeek)
{
@ -539,15 +549,14 @@ public abstract partial class Image
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);
stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize);
memoryStream.Position = 0;
return action(memoryStream);
return Action(memoryStream, 0);
}
/// <summary>
@ -563,7 +572,7 @@ public abstract partial class Image
internal static async Task<T> WithSeekableStreamAsync<T>(
DecoderOptions options,
Stream stream,
Func<Stream, CancellationToken, T> action,
Func<Stream, CancellationToken, Task<T>> action,
CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
@ -574,34 +583,36 @@ public abstract partial class Image
throw new NotSupportedException("Cannot read from the stream.");
}
Configuration configuration = options.Configuration;
if (stream.CanSeek && configuration.ReadOrigin == ReadOrigin.Begin)
async Task<T> Action(Stream s, long position, CancellationToken ct)
{
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
// 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.
}
// 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;
}
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
return result;
}
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
// to allow additional reads.
T result = action(ms, ct);
if (stream.CanSeek && ms.Position != ms.Length)
if (configuration.ReadOrigin == ReadOrigin.Begin)
{
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]
@ -610,7 +621,7 @@ public abstract partial class Image
StringBuilder sb = new();
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);
}

5
src/ImageSharp/Image.cs

@ -41,6 +41,11 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </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(
Configuration configuration,
PixelTypeInfo pixelType,

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

@ -26,6 +26,6 @@ public class IdentifyJpeg
{
using MemoryStream memoryStream = new(this.jpegBytes);
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.
// 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 Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
using Image<TPixel> referenceImage = referenceDecoder.Decode<TPixel>(DecoderOptions.Default, stream);
referenceImage.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.01f),
provider,
@ -309,9 +309,9 @@ public class BmpEncoderTests
// 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.
ImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(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(
ImageComparer.TolerantPercentage(0.01f),
provider,
@ -378,7 +378,7 @@ public class BmpEncoderTests
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null,
ImageComparer customComparer = null,
ImageDecoder referenceDecoder = null)
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
using var stream = new MemoryStream(testFile.Bytes, false);
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;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -156,7 +156,7 @@ public class GifMetadataTests
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
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;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);

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

@ -64,7 +64,7 @@ public class ImageFormatManagerTests
[Fact]
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(null, null));
}
@ -87,14 +87,14 @@ public class ImageFormatManagerTests
[Fact]
public void RegisterMimeTypeDecoderReplacesLast()
{
ImageDecoder decoder1 = new Mock<ImageDecoder>().Object;
IImageDecoder decoder1 = new Mock<IImageDecoder>().Object;
this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1);
ImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat);
IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder1, found);
ImageDecoder decoder2 = new Mock<ImageDecoder>().Object;
IImageDecoder decoder2 = new Mock<IImageDecoder>().Object;
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.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);
using var stream = new MemoryStream(testFile.Bytes, false);
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;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -142,7 +142,7 @@ public partial class JpegDecoderTests
{
var testFile = TestFile.Create(imagePath);
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();
Assert.Equal(quality, meta.Quality);
}
@ -178,25 +178,25 @@ public partial class JpegDecoderTests
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);
using var stream = new MemoryStream(testFile.Bytes, false);
if (useIdentify)
{
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo);
}
else
{
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
test(img);
}
}
private static void TestMetadataImpl(
bool useIdentify,
ImageDecoder decoder,
IImageDecoder decoder,
string imagePath,
int expectedPixelSize,
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);
// 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
// occurs within the encoder itself leaving the input image unaffected.
@ -598,7 +598,7 @@ public partial class PngEncoderTests
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);
}
}

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

@ -16,7 +16,7 @@ public abstract class TiffDecoderBaseTester
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>
{
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")]
public abstract class TiffEncoderBaseTester
{
protected static readonly ImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder();
protected static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
@ -85,7 +85,7 @@ public abstract class TiffEncoderBaseTester
TiffPredictor predictor = TiffPredictor.None,
bool useExactComparer = true,
float compareTolerance = 0.001f,
ImageDecoder imageDecoder = null)
IImageDecoder imageDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
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 Mock<ImageDecoder> localDecoder;
protected Mock<IImageDecoder> localDecoder;
protected IImageFormatDetector localMimeTypeDetector;
@ -59,13 +59,16 @@ public partial class ImageTests
this.localImageInfoMock = new Mock<IImageInfo>();
this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<ImageDecoder>();
this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
this.localDecoder = new Mock<IImageDecoder>();
this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.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
.Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) =>
.Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream>((c, s) =>
{
using var ms = new MemoryStream();
s.CopyTo(ms);
@ -74,8 +77,8 @@ public partial class ImageTests
.Returns(this.localStreamReturnImageRgba32);
this.localDecoder
.Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((c, s, ct) =>
.Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream>((c, s) =>
{
using var ms = new MemoryStream();
s.CopyTo(ms);

6
tests/ImageSharp.Tests/TestFile.cs

@ -147,7 +147,7 @@ public sealed class TestFile
/// <returns>
/// The <see cref="Image{Rgba32}"/>.
/// </returns>
public Image<Rgba32> CreateRgba32Image(ImageDecoder decoder)
public Image<Rgba32> CreateRgba32Image(IImageDecoder decoder)
=> this.CreateRgba32Image(decoder, new());
/// <summary>
@ -158,10 +158,10 @@ public sealed class TestFile
/// <returns>
/// The <see cref="Image{Rgba32}"/>.
/// </returns>
public Image<Rgba32> CreateRgba32Image(ImageDecoder decoder, DecoderOptions options)
public Image<Rgba32> CreateRgba32Image(IImageDecoder decoder, DecoderOptions options)
{
options.Configuration = this.Image.GetConfiguration();
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);
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);
protected internal override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions 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;
var ms = new MemoryStream();
@ -224,7 +224,7 @@ public class TestFormat : IConfigurationModule, IImageFormat
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);
}

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

@ -27,7 +27,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
public Key(
PixelTypes pixelType,
string filePath,
ImageDecoder customDecoder,
IImageDecoder customDecoder,
DecoderOptions options,
ISpecializedDecoderOptions specialized)
{
@ -175,11 +175,11 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
public override Image<TPixel> GetImage()
{
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath);
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath);
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(options, nameof(options));
@ -202,7 +202,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
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(options, nameof(options));
@ -213,10 +213,10 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
// TODO: Check Path here. Why combined?
string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath);
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(options, nameof(options));
@ -239,7 +239,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
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(options, nameof(options));
@ -250,7 +250,7 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
// TODO: Check Path here. Why combined?
string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath);
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)
@ -266,23 +266,23 @@ public abstract partial class TestImageProvider<TPixel> : IXunitSerializable
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;
var testFile = TestFile.Create(this.FilePath);
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()
{
options.GeneralOptions.Configuration = this.Configuration;
var testFile = TestFile.Create(this.FilePath);
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>
public abstract Image<TPixel> GetImage();
public Image<TPixel> GetImage(ImageDecoder decoder)
public Image<TPixel> GetImage(IImageDecoder decoder)
=> this.GetImage(decoder, new());
public Task<Image<TPixel>> GetImageAsync(ImageDecoder decoder)
public Task<Image<TPixel>> GetImageAsync(IImageDecoder decoder)
=> 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}!");
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}!");
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()
=> 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()
=> 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
// state machine and a continuation.
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();
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;
BmpReadDefines bmpReadDefines = new()
@ -32,8 +32,10 @@ public class MagickReferenceDecoder : ImageDecoder
IgnoreFileSize = !this.validate
};
MagickReadSettings settings = new();
settings.FrameCount = (int)options.MaxFrames;
MagickReadSettings settings = new()
{
FrameCount = (int)options.MaxFrames
};
settings.SetDefines(bmpReadDefines);
using MagickImageCollection magickImageCollection = new(stream, settings);
@ -67,10 +69,10 @@ public class MagickReferenceDecoder : ImageDecoder
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);
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);
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();
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);
PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));
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);
if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
@ -45,6 +45,6 @@ public class SystemDrawingReferenceDecoder : ImageDecoder
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);
}

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

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

@ -214,7 +214,7 @@ public static class TestImageExtensions
bool grayscale = false,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true,
ImageDecoder decoder = null)
IImageDecoder decoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> referenceImage = GetReferenceOutputImage<TPixel>(
@ -307,7 +307,7 @@ public static class TestImageExtensions
string extension = "png",
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true,
ImageDecoder decoder = null)
IImageDecoder decoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(
@ -324,7 +324,7 @@ public static class TestImageExtensions
decoder ??= TestEnvironment.GetReferenceDecoder(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>(
@ -343,17 +343,17 @@ public static class TestImageExtensions
var temporaryFrameImages = new List<Image<TPixel>>();
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]);
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]);
foreach (string path in frameFiles)
{
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);
Image<TPixel> tempImage = decoder.Decode<TPixel>(DecoderOptions.Default, stream, default);
Image<TPixel> tempImage = decoder.Decode<TPixel>(DecoderOptions.Default, stream);
temporaryFrameImages.Add(tempImage);
}
@ -511,7 +511,7 @@ public static class TestImageExtensions
public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider,
ImageDecoder referenceDecoder = null)
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
=> CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder);
@ -519,7 +519,7 @@ public static class TestImageExtensions
this Image<TPixel> image,
ITestImageProvider provider,
ImageComparer comparer,
ImageDecoder referenceDecoder = null,
IImageDecoder referenceDecoder = null,
DecoderOptions referenceDecoderOptions = null)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -534,7 +534,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path);
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);
}
@ -546,7 +546,7 @@ public static class TestImageExtensions
this Image<TPixel> image,
ITestImageProvider provider,
ImageComparer comparer,
ImageDecoder referenceDecoder = null)
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
@ -560,7 +560,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path);
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);
}
@ -668,7 +668,7 @@ public static class TestImageExtensions
ImageComparer customComparer = null,
bool appendPixelTypeToFileName = true,
string referenceImageExtension = null,
ImageDecoder referenceDecoder = null)
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
string actualOutputFile = provider.Utility.SaveTestOutputFile(
@ -681,7 +681,7 @@ public static class TestImageExtensions
referenceDecoder ??= TestEnvironment.GetReferenceDecoder(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;
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 sdStream = File.OpenRead(path);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream, default);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream, default);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream);
ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage);
@ -71,8 +71,8 @@ public class MagickReferenceCodecTests
var comparer = ImageComparer.TolerantPercentage(1, 1020);
using FileStream mStream = File.OpenRead(path);
using FileStream sdStream = File.OpenRead(path);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream, default);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream, default);
using Image<TPixel> mImage = magickDecoder.Decode<TPixel>(DecoderOptions.Default, mStream);
using Image<TPixel> sdImage = sdDecoder.Decode<TPixel>(DecoderOptions.Default, sdStream);
ImageSimilarityReport<TPixel, TPixel> report = comparer.CompareImagesOrFrames(mImage, sdImage);
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");
}
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);
measure.Measure(

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

@ -93,7 +93,7 @@ public class SystemDrawingReferenceCodecTests
{
string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash);
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);
}

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

@ -79,7 +79,7 @@ public class TestEnvironmentTests
return;
}
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
Assert.IsType(expectedDecoderType, decoder);
}
@ -113,7 +113,7 @@ public class TestEnvironmentTests
return;
}
ImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
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);
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]++;
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);
protected internal override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options };
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);
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]++;
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);
protected internal override TestDecoderWithParametersOptions CreateDefaultSpecializedOptions(DecoderOptions options)
protected override TestDecoderWithParametersOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options };
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];

Loading…
Cancel
Save