Browse Source

Only expose sanitized decoder methods.

pull/2180/head
James Jackson-South 4 years ago
parent
commit
ac4fb62eeb
  1. 21
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  2. 12
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  3. 2
      src/ImageSharp/Formats/IImageDecoder.cs
  4. 12
      src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs
  5. 65
      src/ImageSharp/Formats/ImageDecoder.cs
  6. 182
      src/ImageSharp/Formats/ImageDecoderExtensions.cs
  7. 82
      src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs
  8. 49
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  9. 21
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  10. 12
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  11. 40
      src/ImageSharp/Formats/Png/PngDecoder.cs
  12. 12
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  13. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs
  14. 12
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  15. 12
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  16. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  17. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  18. 2
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  19. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  20. 2
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  21. 10
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  22. 10
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  23. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  24. 4
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  25. 8
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  26. 68
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  27. 21
      tests/ImageSharp.Tests/TestFormat.cs
  28. 4
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  29. 9
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  30. 9
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  31. 42
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Image decoder for generating an image out of a Windows bitmap stream.
/// </summary>
public class BmpDecoder : ImageDecoder, IImageDecoderSpecialized<BmpDecoderOptions>
public class BmpDecoder : IImageDecoderSpecialized<BmpDecoderOptions>
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -22,29 +22,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
public Image<TPixel> DecodeSpecialized<TPixel>(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Image<TPixel> IImageDecoderSpecialized<BmpDecoderOptions>.Decode<TPixel>(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
Image<TPixel> image = new BmpDecoderCore(options).Decode<TPixel>(options.GeneralOptions.Configuration, stream, cancellationToken);
Resize(options.GeneralOptions, image);
ImageDecoderUtilities.Resize(options.GeneralOptions, image);
return image;
}
/// <inheritdoc/>
public Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgba32>(options, stream, cancellationToken);
Image IImageDecoderSpecialized<BmpDecoderOptions>.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<BmpDecoderOptions>)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Decoder for generating an image out of a gif encoded stream.
/// </summary>
public sealed class GifDecoder : ImageDecoder
public sealed class GifDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
GifDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

2
src/ImageSharp/Formats/IImageDecoder.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats
/// <summary>
/// Encapsulates properties and methods required for decoding an image from a stream.
/// </summary>
public interface IImageDecoder
public interface IImageDecoder : IImageInfoDetector
{
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.

12
src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
@ -17,23 +17,29 @@ namespace SixLabors.ImageSharp.Formats
/// <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>
public Image<TPixel> DecodeSpecialized<TPixel>(T options, Stream stream, CancellationToken cancellationToken)
public 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>
public Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken);
public Image Decode(T options, Stream stream, CancellationToken cancellationToken);
}
}

65
src/ImageSharp/Formats/ImageDecoder.cs

@ -1,65 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// The base class for all image decoders.
/// </summary>
public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder
{
/// <inheritdoc/>
public abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <inheritdoc/>
public abstract Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
/// <inheritdoc/>
public abstract Image Decode(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
/// already matches the target size, the image is untouched.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
protected static void Resize(DecoderOptions options, Image image)
{
if (ShouldResize(options, image))
{
ResizeOptions resizeOptions = new()
{
Size = options.TargetSize.Value,
Sampler = KnownResamplers.Box,
Mode = ResizeMode.Max
};
image.Mutate(x => x.Resize(resizeOptions));
}
}
/// <summary>
/// Determines whether the decoded image should be resized.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
/// <returns><see langword="true"/> if the image should be resized, otherwise; <see langword="false"/>.</returns>
private static bool ShouldResize(DecoderOptions options, Image image)
{
if (options.TargetSize is null)
{
return false;
}
Size targetSize = options.TargetSize.Value;
Size currentSize = image.Size();
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
}
}
}

182
src/ImageSharp/Formats/ImageDecoderExtensions.cs

@ -0,0 +1,182 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Extensions methods for <see cref="IImageDecoder"/> and <see cref="IImageDecoderSpecialized{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 IImageDecoder 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 IImageDecoder 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 IImageDecoder 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 IImageDecoder 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 IImageDecoder 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 IImageDecoder 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 IImageDecoderSpecialized<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 IImageDecoderSpecialized<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 IImageDecoderSpecialized<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 IImageDecoderSpecialized<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);
}
}

82
src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs

@ -1,82 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Extensions methods for <see cref="IImageDecoderSpecialized{T}"/>.
/// </summary>
public static class ImageDecoderSpecializedExtensions
{
/// <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> DecodeSpecialized<T, TPixel>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.DecodeSpecialized<TPixel>(options, stream, 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 DecodeSpecialized<T>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream)
where T : ISpecializedDecoderOptions
=> decoder.DecodeSpecialized(options, stream, 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>> DecodeSpecializedAsync<T, TPixel>(this IImageDecoderSpecialized<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.DecodeSpecialized<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> DecodeSpecializedAsync<T>(this IImageDecoderSpecialized<T> decoder, T options, Stream stream, CancellationToken cancellationToken = default)
where T : ISpecializedDecoderOptions
=> Image.WithSeekableStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => decoder.DecodeSpecialized(options, s, ct),
cancellationToken);
}
}

49
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -7,12 +7,55 @@ using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Utility methods for <see cref="IImageDecoder"/>.
/// </summary>
internal static class ImageDecoderUtilities
{
public static IImageInfo Identify(
/// <summary>
/// Performs a resize operation against the decoded image. If the target size is not set, or the image size
/// already matches the target size, the image is untouched.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
public static void Resize(DecoderOptions options, Image image)
{
if (ShouldResize(options, image))
{
ResizeOptions resizeOptions = new()
{
Size = options.TargetSize.Value,
Sampler = KnownResamplers.Box,
Mode = ResizeMode.Max
};
image.Mutate(x => x.Resize(resizeOptions));
}
}
/// <summary>
/// Determines whether the decoded image should be resized.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="image">The decoded image.</param>
/// <returns><see langword="true"/> if the image should be resized, otherwise; <see langword="false"/>.</returns>
private static bool ShouldResize(DecoderOptions options, Image image)
{
if (options.TargetSize is null)
{
return false;
}
Size targetSize = options.TargetSize.Value;
Size currentSize = image.Size();
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
}
internal static IImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
@ -30,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats
}
}
public static Image<TPixel> Decode<TPixel>(
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,
@ -38,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats
where TPixel : unmanaged, IPixel<TPixel>
=> decoder.Decode<TPixel>(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken);
public static Image<TPixel> Decode<TPixel>(
internal static Image<TPixel> Decode<TPixel>(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Decoder for generating an image out of a jpeg encoded stream.
/// </summary>
public sealed class JpegDecoder : ImageDecoder, IImageDecoderSpecialized<JpegDecoderOptions>
public sealed class JpegDecoder : IImageDecoderSpecialized<JpegDecoderOptions>
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
@ -22,16 +22,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken);
/// <inheritdoc/>
public Image<TPixel> DecodeSpecialized<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
Image<TPixel> IImageDecoderSpecialized<JpegDecoderOptions>.Decode<TPixel>(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -41,14 +40,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly)
{
Resize(options.GeneralOptions, image);
ImageDecoderUtilities.Resize(options.GeneralOptions, image);
}
return image;
}
/// <inheritdoc/>
public Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgb24>(options, stream, cancellationToken);
Image IImageDecoderSpecialized<JpegDecoderOptions>.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<JpegDecoderOptions>)this).Decode<Rgb24>(options, stream, cancellationToken);
}
}

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

@ -26,10 +26,10 @@ namespace SixLabors.ImageSharp.Formats.Pbm
/// </list>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary>
public sealed class PbmDecoder : ImageDecoder
public sealed class PbmDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm
}
/// <inheritdoc />
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -46,13 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Pbm
PbmDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc />
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgb24>(options, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgb24>(options, stream, cancellationToken);
}
}

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Decoder for generating an image out of a png encoded stream.
/// </summary>
public sealed class PngDecoder : ImageDecoder
public sealed class PngDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Png
PngDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -48,47 +48,49 @@ namespace SixLabors.ImageSharp.Formats.Png
PngMetadata meta = info.Metadata.GetPngMetadata();
PngColorType color = meta.ColorType.GetValueOrDefault();
PngBitDepth bits = meta.BitDepth.GetValueOrDefault();
var imageDecoder = (IImageDecoder)this;
switch (color)
{
case PngColorType.Grayscale:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<L16>(options, stream, cancellationToken)
: this.Decode<La32>(options, stream, cancellationToken);
? imageDecoder.Decode<L16>(options, stream, cancellationToken)
: imageDecoder.Decode<La32>(options, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<L8>(options, stream, cancellationToken)
: this.Decode<La16>(options, stream, cancellationToken);
? imageDecoder.Decode<L8>(options, stream, cancellationToken)
: imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.Rgb:
if (bits == PngBitDepth.Bit16)
{
return !meta.HasTransparency
? this.Decode<Rgb48>(options, stream, cancellationToken)
: this.Decode<Rgba64>(options, stream, cancellationToken);
? imageDecoder.Decode<Rgb48>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba64>(options, stream, cancellationToken);
}
return !meta.HasTransparency
? this.Decode<Rgb24>(options, stream, cancellationToken)
: this.Decode<Rgba32>(options, stream, cancellationToken);
? imageDecoder.Decode<Rgb24>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.Palette:
return this.Decode<Rgba32>(options, stream, cancellationToken);
return imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
case PngColorType.GrayscaleWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<La32>(options, stream, cancellationToken)
: this.Decode<La16>(options, stream, cancellationToken);
? imageDecoder.Decode<La32>(options, stream, cancellationToken)
: imageDecoder.Decode<La16>(options, stream, cancellationToken);
case PngColorType.RgbWithAlpha:
return (bits == PngBitDepth.Bit16)
? this.Decode<Rgba64>(options, stream, cancellationToken)
: this.Decode<Rgba32>(options, stream, cancellationToken);
? imageDecoder.Decode<Rgba64>(options, stream, cancellationToken)
: imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
default:
return this.Decode<Rgba32>(options, stream, cancellationToken);
return imageDecoder.Decode<Rgba32>(options, stream, cancellationToken);
}
}
}

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <summary>
/// Image decoder for Truevision TGA images.
/// </summary>
public sealed class TgaDecoder : ImageDecoder
public sealed class TgaDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tga
TgaDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <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 Image<Rgb24> image = ((IImageDecoder)new WebpDecoder()).Decode<Rgb24>(this.options, stream, cancellationToken);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : ImageDecoder
public class TiffDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary>
/// Image decoder for generating an image out of a webp stream.
/// </summary>
public sealed class WebpDecoder : ImageDecoder
public sealed class WebpDecoder : IImageDecoder
{
/// <inheritdoc/>
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
}
/// <inheritdoc/>
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
Image<TPixel> IImageDecoder.Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -31,13 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Webp
using WebpDecoderCore decoder = new(options);
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken);
Resize(options, image);
ImageDecoderUtilities.Resize(options, image);
return image;
}
/// <inheritdoc/>
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoder)this).Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private void GenericBechmark()
{
this.preloadedImageStream.Position = 0;
using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream, default);
using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream);
}
[GlobalSetup(Target = nameof(JpegBaselineInterleaved444))]

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

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public IImageInfo Identify()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder();
IImageDecoder decoder = new JpegDecoder();
return decoder.Identify(DecoderOptions.Default, memoryStream, default);
}
}

2
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
};
var decoder = new JpegDecoder();
using ImageSharpImage image = decoder.Decode(options, inputStream, default);
using ImageSharpImage image = decoder.Decode(options, inputStream);
this.LogImageProcessed(image.Width, image.Height);
// Reduce the size of the file

2
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new BmpDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);

2
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
fixed (byte* data = testFile.Bytes.AsSpan(0, length))
{
using var stream = new UnmanagedMemoryStream(data, length);
using Image<Rgba32> image = GifDecoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> image = GifDecoder.Decode<Rgba32>(DecoderOptions.Default, stream);
Assert.Equal((200, 200), (image.Width, image.Height));
}
}

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

@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
GifMetadata metadata = image.Metadata.GetGifMetadata();
Assert.Equal(2, metadata.Comments.Count);
Assert.Equal(new string('c', 349), metadata.Comments[0]);
@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new GifDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}

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

@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
using Image image = decoder.Decode(DecoderOptions.Default, stream, default);
using Image image = decoder.Decode(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new JpegDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream, default);
using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(DecoderOptions.Default, memStream, default));
Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(DecoderOptions.Default, memStream));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
}

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

@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
Image image = decoder.Decode(DecoderOptions.Default, stream, default);
Image image = decoder.Decode(DecoderOptions.Default, stream);
PngMetadata metadata = image.Metadata.GetPngMetadata();
Assert.Equal(pngColorType, metadata.ColorType);
@ -597,7 +597,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
// occurs within the encoder itself leaving the input image unaffected.
// This means we are benefiting from testing our decoder also.
using FileStream fileStream = File.OpenRead(actualOutputFile);
using Image<TPixel> imageSharpImage = new PngDecoder().Decode<TPixel>(DecoderOptions.Default, fileStream, default);
using Image<TPixel> imageSharpImage = new PngDecoder().Decode<TPixel>(DecoderOptions.Default, fileStream);
fileStream.Position = 0;

8
tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
}
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
});
memoryStream.Position = 0;
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, memoryStream);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
Assert.Contains(meta.TextData, m => m.Equals(expectedText));
Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin));
@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new PngDecoder();
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream, default);
using Image<Rgba32> image = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
var decoder = new PngDecoder();
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default);
IImageInfo image = decoder.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);

68
tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

@ -26,76 +26,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms, default);
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms);
ImageComparer.Tolerant().VerifySimilarity(image, img2);
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
}
/* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the
paletted image has alpha of 0
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
public void CanSaveIndexedPng<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder() { PaletteSize = 256 });
ms.Position = 0;
using (Image<Rgba32> img2 = Image.Load<Rgba32>(ms, new PngDecoder()))
{
ImageComparer.VerifySimilarity(image, img2, 0.03f);
}
}
}*/
/* JJS: Commented out for now since the test does not take into lossy nature of indexing.
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.Color)]
public void CanSaveIndexedPngTwice<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// does saving a file then reopening mean both files are identical???
using (Image<TPixel> source = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
source.Metadata.Quality = 256;
source.Save(ms, new PngEncoder(), new PngEncoderOptions {
Threshold = 200
});
ms.Position = 0;
using (Image img1 = Image.Load(ms, new PngDecoder()))
{
using (MemoryStream ms2 = new MemoryStream())
{
img1.Save(ms2, new PngEncoder(), new PngEncoderOptions
{
Threshold = 200
});
ms2.Position = 0;
using (Image img2 = Image.Load(ms2, new PngDecoder()))
{
using (PixelAccessor<Color> pixels1 = img1.Lock())
using (PixelAccessor<Color> pixels2 = img2.Lock())
{
for (int y = 0; y < img1.Height; y++)
{
for (int x = 0; x < img1.Width; x++)
{
Assert.Equal(pixels1[x, y], pixels2[x, y]);
}
}
}
}
}
}
}
}*/
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.Rgba32)]
public void Resize<TPixel>(TestImageProvider<TPixel> provider)
@ -111,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
// image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms, default);
using Image<Rgba32> img2 = new PngDecoder().Decode<Rgba32>(DecoderOptions.Default, ms);
ImageComparer.Tolerant().VerifySimilarity(image, img2);
}
}

21
tests/ImageSharp.Tests/TestFormat.cs

@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests
public TestHeader(TestFormat testFormat) => this.testFormat = testFormat;
}
public class TestDecoder : ImageDecoder, IImageDecoderSpecialized<TestDecoderOptions>
public class TestDecoder : IImageDecoderSpecialized<TestDecoderOptions>
{
private readonly TestFormat testFormat;
@ -208,16 +208,17 @@ namespace SixLabors.ImageSharp.Tests
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TestPixelForAgnosticDecode>(new() { GeneralOptions = options }, stream, cancellationToken);
public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<TestDecoderOptions>)this).Decode<TestPixelForAgnosticDecode>(new() { GeneralOptions = options }, stream, cancellationToken);
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> ((IImageDecoderSpecialized<TestDecoderOptions>)this).Decode<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TestPixelForAgnosticDecode>(new() { GeneralOptions = options }, stream, cancellationToken);
public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> ((IImageDecoderSpecialized<TestDecoderOptions>)this).Decode<TestPixelForAgnosticDecode>(new() { GeneralOptions = options }, stream, cancellationToken);
public Image<TPixel> DecodeSpecialized<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = options.GeneralOptions.Configuration;
@ -235,8 +236,8 @@ namespace SixLabors.ImageSharp.Tests
return this.testFormat.Sample<TPixel>();
}
public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TestPixelForAgnosticDecode>(options, stream, cancellationToken);
public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TestPixelForAgnosticDecode>(options, stream, cancellationToken);
}
public class TestDecoderOptions : ISpecializedDecoderOptions

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

@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests
// 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.DecodeSpecialized<TPixel>(options, stream, default));
return Task.FromResult(decoder.Decode<TPixel>(options, stream, default));
}
public override void Deserialize(IXunitSerializationInfo info)
@ -286,7 +286,7 @@ namespace SixLabors.ImageSharp.Tests
var testFile = TestFile.Create(this.FilePath);
using Stream stream = new MemoryStream(testFile.Bytes);
return decoder.DecodeSpecialized<TPixel>(options, stream, default);
return decoder.Decode<TPixel>(options, stream, default);
}
}

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

@ -15,7 +15,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
public class MagickReferenceDecoder : ImageDecoder
public class MagickReferenceDecoder : IImageDecoder
{
private readonly bool validate;
@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
public static MagickReferenceDecoder Instance { get; } = new();
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>
{
Configuration configuration = options.Configuration;
var bmpReadDefines = new BmpReadDefines
@ -70,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
return new Image<TPixel>(configuration, new ImageMetadata(), framesList);
}
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public 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)

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

@ -11,18 +11,19 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
public class SystemDrawingReferenceDecoder : ImageDecoder
public class SystemDrawingReferenceDecoder : IImageDecoder
{
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
using var sourceBitmap = new SDBitmap(stream);
PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));
return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata());
}
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using var sourceBitmap = new SDBitmap(stream);
if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap<TPixel>(convertedBitmap);
}
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
}
}

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

@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp.Tests
}
}
private class TestDecoder : ImageDecoder, IImageDecoderSpecialized<TestDecoderOptions>
private class TestDecoder : IImageDecoderSpecialized<TestDecoderOptions>
{
// Couldn't make xUnit happy without this hackery:
private static readonly ConcurrentDictionary<string, int> InvocationCounts = new();
@ -368,24 +368,25 @@ namespace SixLabors.ImageSharp.Tests
}
}
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgba32>(new() { GeneralOptions = options }, stream, cancellationToken);
public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.Decode<TPixel>((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken);
public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public Image<TPixel> DecodeSpecialized<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgba32>(options, stream, cancellationToken);
public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];
@ -396,7 +397,7 @@ namespace SixLabors.ImageSharp.Tests
}
}
private class TestDecoderWithParameters : ImageDecoder, IImageDecoderSpecialized<TestDecoderWithParametersOptions>
private class TestDecoderWithParameters : IImageDecoderSpecialized<TestDecoderWithParametersOptions>
{
private static readonly ConcurrentDictionary<string, int> InvocationCounts = new();
@ -412,24 +413,25 @@ namespace SixLabors.ImageSharp.Tests
}
}
public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgba32>(new() { GeneralOptions = options }, stream, cancellationToken);
public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<TPixel>(new() { GeneralOptions = options }, stream, cancellationToken);
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.Decode<TPixel>((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken);
public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken);
public Image<TPixel> DecodeSpecialized<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
public Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
InvocationCounts[this.callerName]++;
return new Image<TPixel>(42, 42);
}
public Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
=> this.DecodeSpecialized<Rgba32>(options, stream, cancellationToken);
public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName];

Loading…
Cancel
Save