Browse Source

Re-introduce IImageEncoder and split encoding pipelines.

pull/2301/head
James Jackson-South 4 years ago
parent
commit
c550af8c5a
  1. 8
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 6
      src/ImageSharp/Advanced/AotCompilerTools.cs
  3. 11
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  4. 11
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  5. 26
      src/ImageSharp/Formats/IImageEncoder.cs
  6. 4
      src/ImageSharp/Formats/IImageEncoderInternals.cs
  7. 96
      src/ImageSharp/Formats/ImageDecoder.cs
  8. 56
      src/ImageSharp/Formats/ImageEncoderUtilities.cs
  9. 16
      src/ImageSharp/Formats/ImageFormatManager.cs
  10. 13
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  11. 15
      src/ImageSharp/Formats/Pbm/PbmEncoder.cs
  12. 14
      src/ImageSharp/Formats/Png/PngEncoder.cs
  13. 23
      src/ImageSharp/Formats/QuantizingImageEncoder.cs
  14. 94
      src/ImageSharp/Formats/SynchronousImageEncoder.cs
  15. 13
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  16. 11
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  17. 13
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  18. 8
      src/ImageSharp/Image.cs
  19. 18
      src/ImageSharp/ImageExtensions.cs
  20. 10
      tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
  21. 8
      tests/ImageSharp.Tests/Image/ImageSaveTests.cs
  22. 2
      tests/ImageSharp.Tests/Image/ImageTests.Save.cs
  23. 4
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  24. 2
      tests/ImageSharp.Tests/Image/ImageTests.cs
  25. 2
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  26. 2
      tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs
  27. 12
      tests/ImageSharp.Tests/TestFormat.cs
  28. 4
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  29. 32
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
  30. 11
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs
  31. 8
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  32. 10
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  33. 4
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

8
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -21,8 +21,8 @@ public static class AdvancedImageExtensions
/// <param name="filePath">The target file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The file path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>The matching <see cref="ImageEncoder"/>.</returns>
public static ImageEncoder DetectEncoder(this Image source, string filePath)
/// <returns>The matching <see cref="IImageEncoder"/>.</returns>
public static IImageEncoder DetectEncoder(this Image source, string filePath)
{
Guard.NotNull(filePath, nameof(filePath));
@ -40,13 +40,13 @@ public static class AdvancedImageExtensions
throw new NotSupportedException(sb.ToString());
}
ImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
{
StringBuilder sb = new();
sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, ImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
}

6
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -230,7 +230,7 @@ internal static class AotCompilerTools
}
/// <summary>
/// This method pre-seeds the all <see cref="ImageEncoder"/> in the AoT compiler.
/// This method pre-seeds the all <see cref="IImageEncoder"/> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
@ -266,14 +266,14 @@ internal static class AotCompilerTools
}
/// <summary>
/// This method pre-seeds the <see cref="ImageEncoder"/> in the AoT compiler.
/// This method pre-seeds the <see cref="IImageEncoder"/> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TEncoder">The encoder.</typeparam>
[Preserve]
private static void AotCompileImageEncoder<TPixel, TEncoder>()
where TPixel : unmanaged, IPixel<TPixel>
where TEncoder : ImageEncoder
where TEncoder : IImageEncoder
{
default(TEncoder).Encode<TPixel>(default, default);
default(TEncoder).EncodeAsync<TPixel>(default, default, default);

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

@ -24,16 +24,9 @@ public sealed class BmpEncoder : QuantizingImageEncoder
public bool SupportTransparency { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
BmpEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

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

@ -16,16 +16,9 @@ public sealed class GifEncoder : QuantizingImageEncoder
public GifColorTableMode? ColorTableMode { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
GifEncoderCore encoder = new(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
GifEncoderCore encoder = new(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

26
src/ImageSharp/Formats/ImageEncoder.cs → src/ImageSharp/Formats/IImageEncoder.cs

@ -2,15 +2,13 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// The base class for all image encoders.
/// Defines the contract for all image encoders.
/// </summary>
public abstract class ImageEncoder
public interface IImageEncoder
{
/// <summary>
/// Gets a value indicating whether to ignore decoded metadata when encoding.
@ -23,7 +21,7 @@ public abstract class ImageEncoder
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}" /> to encode from.</param>
/// <param name="stream">The <see cref="Stream" /> to encode the image data to.</param>
public abstract void Encode<TPixel>(Image<TPixel> image, Stream stream)
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
@ -34,22 +32,6 @@ public abstract class ImageEncoder
/// <param name="stream">The <see cref="Stream" /> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
public abstract Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
}
/// <summary>
/// The base class for all image encoders that allow color palette generation via quantization.
/// </summary>
public abstract class QuantizingImageEncoder : ImageEncoder
{
/// <summary>
/// Gets the quantizer used to generate the color palette.
/// </summary>
public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree;
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
/// </summary>
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
}

4
src/ImageSharp/Formats/IImageEncoderInternals.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Abstraction for shared internals for ***DecoderCore implementations to be used with <see cref="ImageEncoderUtilities"/>.
/// Abstraction for shared internals for ***DecoderCore implementations.
/// </summary>
internal interface IImageEncoderInternals
{

96
src/ImageSharp/Formats/ImageDecoder.cs

@ -13,6 +13,53 @@ namespace SixLabors.ImageSharp.Formats;
/// </summary>
public abstract class ImageDecoder : IImageDecoder
{
/// <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));
/// <inheritdoc/>
public Image Decode(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
s => this.Decode(options, s, default));
/// <inheritdoc/>
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/>
public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
/// <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);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.
/// </summary>
@ -93,53 +140,6 @@ public abstract class ImageDecoder : IImageDecoder
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
}
/// <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));
/// <inheritdoc/>
public Image Decode(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
s => this.Decode(options, s, default));
/// <inheritdoc/>
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/>
public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> WithSeekableStreamAsync(
options,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
/// <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,
@ -213,7 +213,7 @@ public abstract class ImageDecoder : IImageDecoder
// 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?
// Perhaps we can make overloads accepting the chunked memorystream? Or maybe AOT is good with pattern matching against types?
Configuration configuration = options.Configuration;
using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator);
await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);

56
src/ImageSharp/Formats/ImageEncoderUtilities.cs

@ -1,56 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
internal static class ImageEncoderUtilities
{
public static async Task EncodeAsync<TPixel>(
this IImageEncoderInternals encoder,
Image<TPixel> image,
Stream stream,
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = image.GetConfiguration();
if (stream.CanSeek)
{
await DoEncodeAsync(stream).ConfigureAwait(false);
}
else
{
using MemoryStream ms = new();
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
.ConfigureAwait(false);
}
Task DoEncodeAsync(Stream innerStream)
{
try
{
encoder.Encode(image, innerStream, cancellationToken);
return Task.CompletedTask;
}
catch (OperationCanceledException)
{
return Task.FromCanceled(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}
}
public static void Encode<TPixel>(
this IImageEncoderInternals encoder,
Image<TPixel> image,
Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> encoder.Encode(image, stream, default);
}

16
src/ImageSharp/Formats/ImageFormatManager.cs

@ -17,9 +17,9 @@ public class ImageFormatManager
private static readonly object HashLock = new();
/// <summary>
/// The list of supported <see cref="ImageEncoder"/> keyed to mime types.
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </summary>
private readonly ConcurrentDictionary<IImageFormat, ImageEncoder> mimeTypeEncoders = new();
private readonly ConcurrentDictionary<IImageFormat, IImageEncoder> mimeTypeEncoders = new();
/// <summary>
/// The list of supported <see cref="IImageDecoder"/> keyed to mime types.
@ -64,9 +64,9 @@ public class ImageFormatManager
internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
/// <summary>
/// Gets the currently registered <see cref="ImageEncoder"/>s.
/// Gets the currently registered <see cref="IImageEncoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, ImageEncoder>> ImageEncoders => this.mimeTypeEncoders;
internal IEnumerable<KeyValuePair<IImageFormat, IImageEncoder>> ImageEncoders => this.mimeTypeEncoders;
/// <summary>
/// Registers a new format provider.
@ -117,7 +117,7 @@ public class ImageFormatManager
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="encoder">The encoder to use,</param>
public void SetEncoder(IImageFormat imageFormat, ImageEncoder encoder)
public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(encoder, nameof(encoder));
@ -172,12 +172,12 @@ public class ImageFormatManager
/// For the specified mime type find the encoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="ImageEncoder"/> if found otherwise null</returns>
public ImageEncoder FindEncoder(IImageFormat format)
/// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns>
public IImageEncoder FindEncoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
return this.mimeTypeEncoders.TryGetValue(format, out ImageEncoder encoder)
return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)
? encoder
: null;
}

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg;
/// <summary>
/// Encoder for writing the data image to a stream in jpeg format.
/// </summary>
public sealed class JpegEncoder : ImageEncoder
public sealed class JpegEncoder : SynchronousImageEncoder
{
/// <summary>
/// Backing field for <see cref="Quality"/>.
@ -48,16 +48,9 @@ public sealed class JpegEncoder : ImageEncoder
public JpegEncodingColor? ColorType { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
JpegEncoderCore encoder = new(this);
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
JpegEncoderCore encoder = new(this);
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

15
src/ImageSharp/Formats/Pbm/PbmEncoder.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm;
/// </para>
/// The specification of these images is found at <seealso href="http://netpbm.sourceforge.net/doc/pnm.html"/>.
/// </summary>
public sealed class PbmEncoder : ImageEncoder
public sealed class PbmEncoder : SynchronousImageEncoder
{
/// <summary>
/// Gets the encoding of the pixels.
@ -42,21 +42,14 @@ public sealed class PbmEncoder : ImageEncoder
public PbmColorType? ColorType { get; init; }
/// <summary>
/// Gets the Data Type of the pixel components.
/// Gets the data type of the pixel components.
/// </summary>
public PbmComponentType? ComponentType { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
PbmEncoderCore encoder = new(image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
PbmEncoderCore encoder = new(image.GetConfiguration(), this);
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

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

@ -74,19 +74,9 @@ public class PngEncoder : QuantizingImageEncoder
public PngTransparentColorMode TransparentColorMode { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this);
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this);
await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
encoder.Encode(image, stream, cancellationToken);
}
}

23
src/ImageSharp/Formats/QuantizingImageEncoder.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Acts as a base class for all image encoders that allow color palette generation via quantization.
/// </summary>
public abstract class QuantizingImageEncoder : SynchronousImageEncoder
{
/// <summary>
/// Gets the quantizer used to generate the color palette.
/// </summary>
public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree;
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
/// </summary>
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
}

94
src/ImageSharp/Formats/SynchronousImageEncoder.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Acts as a base class for image encoders.
/// Types that inherit this encoder are required to implement cancellable synchronous encoding operations only.
/// </summary>
public abstract class SynchronousImageEncoder : IImageEncoder
{
/// <inheritdoc/>
public bool SkipMetadata { get; init; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> this.EncodeWithSeekableStream(image, stream, default);
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> this.EncodeWithSeekableStreamAsync(image, stream, cancellationToken);
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}" />.
/// </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="image">The <see cref="Image{TPixel}" /> to encode from.</param>
/// <param name="stream">The <see cref="Stream" /> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
protected abstract void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>;
private void EncodeWithSeekableStream<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = image.GetConfiguration();
if (stream.CanSeek)
{
this.Encode(image, stream, cancellationToken);
}
else
{
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
this.Encode(image, stream, cancellationToken);
ms.Position = 0;
ms.CopyTo(stream, configuration.StreamProcessingBufferSize);
}
}
private async Task EncodeWithSeekableStreamAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = image.GetConfiguration();
if (stream.CanSeek)
{
await DoEncodeAsync(stream).ConfigureAwait(false);
}
else
{
using ChunkedMemoryStream ms = new(configuration.MemoryAllocator);
await DoEncodeAsync(ms);
ms.Position = 0;
await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
.ConfigureAwait(false);
}
Task DoEncodeAsync(Stream innerStream)
{
try
{
// TODO: Are synchronous IO writes OK? We avoid reads.
this.Encode(image, innerStream, cancellationToken);
return Task.CompletedTask;
}
catch (OperationCanceledException)
{
return Task.FromCanceled(cancellationToken);
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}
}
}

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

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tga;
/// <summary>
/// Image encoder for writing an image to a stream as a targa truevision image.
/// </summary>
public sealed class TgaEncoder : ImageEncoder
public sealed class TgaEncoder : SynchronousImageEncoder
{
/// <summary>
/// Gets the number of bits per pixel.
@ -21,16 +21,9 @@ public sealed class TgaEncoder : ImageEncoder
public TgaCompression Compression { get; init; } = TgaCompression.RunLength;
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TgaEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TgaEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

11
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -40,16 +40,9 @@ public class TiffEncoder : QuantizingImageEncoder
public TiffPredictor? HorizontalPredictor { get; init; }
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TiffEncoderCore encode = new(this, image.GetMemoryAllocator());
encode.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
TiffEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
encode.Encode(image, stream, cancellationToken);
}
}

13
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Image encoder for writing an image to a stream in the Webp format.
/// </summary>
public sealed class WebpEncoder : ImageEncoder
public sealed class WebpEncoder : SynchronousImageEncoder
{
/// <summary>
/// Gets the webp file format used. Either lossless or lossy.
@ -80,16 +80,9 @@ public sealed class WebpEncoder : ImageEncoder
public int NearLosslessQuality { get; init; } = 100;
/// <inheritdoc/>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
}
/// <inheritdoc/>
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
return encoder.EncodeAsync(image, stream, cancellationToken);
encoder.Encode(image, stream, cancellationToken);
}
}

8
src/ImageSharp/Image.cs

@ -101,7 +101,7 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream or encoder is null.</exception>
public void Save(Stream stream, ImageEncoder encoder)
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder));
@ -118,7 +118,7 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream or encoder is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task SaveAsync(Stream stream, ImageEncoder encoder, CancellationToken cancellationToken = default)
public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoder, nameof(encoder));
@ -189,11 +189,11 @@ public abstract partial class Image : IImage, IConfigurationProvider
private class EncodeVisitor : IImageVisitor, IImageVisitorAsync
{
private readonly ImageEncoder encoder;
private readonly IImageEncoder encoder;
private readonly Stream stream;
public EncodeVisitor(ImageEncoder encoder, Stream stream)
public EncodeVisitor(IImageEncoder encoder, Stream stream)
{
this.encoder = encoder;
this.stream = stream;

18
src/ImageSharp/ImageExtensions.cs

@ -43,14 +43,12 @@ public static partial class ImageExtensions
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The encoder is null.</exception>
public static void Save(this Image source, string path, ImageEncoder encoder)
public static void Save(this Image source, string path, IImageEncoder encoder)
{
Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder));
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
source.Save(fs, encoder);
}
using Stream fs = source.GetConfiguration().FileSystem.Create(path);
source.Save(fs, encoder);
}
/// <summary>
@ -66,7 +64,7 @@ public static partial class ImageExtensions
public static async Task SaveAsync(
this Image source,
string path,
ImageEncoder encoder,
IImageEncoder encoder,
CancellationToken cancellationToken = default)
{
Guard.NotNull(path, nameof(path));
@ -96,14 +94,14 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream.");
}
ImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
{
StringBuilder sb = new();
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, ImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
@ -140,14 +138,14 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream.");
}
ImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
{
StringBuilder sb = new();
sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, ImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}

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

@ -56,7 +56,7 @@ public class ImageFormatManagerTests
[Fact]
public void RegisterNullMimeTypeEncoder()
{
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetEncoder(null, new Mock<ImageEncoder>().Object));
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetEncoder(null, new Mock<IImageEncoder>().Object));
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null));
Assert.Throws<ArgumentNullException>(() => this.DefaultFormatsManager.SetEncoder(null, null));
}
@ -72,14 +72,14 @@ public class ImageFormatManagerTests
[Fact]
public void RegisterMimeTypeEncoderReplacesLast()
{
ImageEncoder encoder1 = new Mock<ImageEncoder>().Object;
IImageEncoder encoder1 = new Mock<IImageEncoder>().Object;
this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1);
ImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat);
IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat);
Assert.Equal(encoder1, found);
ImageEncoder encoder2 = new Mock<ImageEncoder>().Object;
IImageEncoder encoder2 = new Mock<IImageEncoder>().Object;
this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2);
ImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat);
IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat);
Assert.Equal(encoder2, found2);
Assert.NotEqual(found, found2);
}

8
tests/ImageSharp.Tests/Image/ImageSaveTests.cs

@ -16,8 +16,8 @@ public class ImageSaveTests : IDisposable
{
private readonly Image<Rgba32> image;
private readonly Mock<IFileSystem> fileSystem;
private readonly Mock<ImageEncoder> encoder;
private readonly Mock<ImageEncoder> encoderNotInFormat;
private readonly Mock<IImageEncoder> encoder;
private readonly Mock<IImageEncoder> encoderNotInFormat;
private IImageFormatDetector localMimeTypeDetector;
private Mock<IImageFormat> localImageFormat;
@ -27,9 +27,9 @@ public class ImageSaveTests : IDisposable
this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" });
this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object);
this.encoder = new Mock<ImageEncoder>();
this.encoder = new Mock<IImageEncoder>();
this.encoderNotInFormat = new Mock<ImageEncoder>();
this.encoderNotInFormat = new Mock<IImageEncoder>();
this.fileSystem = new Mock<IFileSystem>();
var config = new Configuration

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

@ -68,7 +68,7 @@ public partial class ImageTests
{
using var image = new Image<Rgba32>(5, 5);
image.Dispose();
ImageEncoder encoder = Mock.Of<ImageEncoder>();
IImageEncoder encoder = Mock.Of<IImageEncoder>();
using (var stream = new MemoryStream())
{
Assert.Throws<ObjectDisposedException>(() => image.Save(stream, encoder));

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

@ -102,7 +102,7 @@ public partial class ImageTests
{
var image = new Image<Rgba32>(5, 5);
image.Dispose();
ImageEncoder encoder = Mock.Of<ImageEncoder>();
IImageEncoder encoder = Mock.Of<IImageEncoder>();
using (var stream = new MemoryStream())
{
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await image.SaveAsync(stream, encoder));
@ -120,7 +120,7 @@ public partial class ImageTests
{
using (var image = new Image<Rgba32>(5, 5))
{
ImageEncoder encoder = image.DetectEncoder(filename);
IImageEncoder encoder = image.DetectEncoder(filename);
using (var stream = new MemoryStream())
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);

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

@ -328,7 +328,7 @@ public partial class ImageTests
public void KnownExtension_ReturnsEncoder()
{
using var image = new Image<L8>(1, 1);
ImageEncoder encoder = image.DetectEncoder("dummy.png");
IImageEncoder encoder = image.DetectEncoder("dummy.png");
Assert.NotNull(encoder);
Assert.IsType<PngEncoder>(encoder);
}

2
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -55,7 +55,7 @@ public class LargeImageIntegrationTests
Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true;
ImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(
configuration.ImageFormatsManager.FindFormatByFileExtension(formatInner));
string dir = TestEnvironment.CreateOutputDirectory(".Temp");
string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}");

2
tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

@ -242,7 +242,7 @@ public class XmpProfileTests
return profile;
}
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, ImageEncoder encoder)
private static Image<Rgba32> WriteAndRead(Image<Rgba32> image, IImageEncoder encoder)
{
using (var memStream = new MemoryStream())
{

12
tests/ImageSharp.Tests/TestFormat.cs

@ -233,7 +233,7 @@ public class TestFormat : IConfigurationModule, IImageFormat
public DecoderOptions GeneralOptions { get; init; } = DecoderOptions.Default;
}
public class TestEncoder : ImageEncoder
public class TestEncoder : IImageEncoder
{
private readonly TestFormat testFormat;
@ -243,13 +243,17 @@ public class TestFormat : IConfigurationModule, IImageFormat
public IEnumerable<string> FileExtensions => this.testFormat.SupportedExtensions;
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
public bool SkipMetadata { get; init; }
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO record this happened so we can verify it.
}
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
=> Task.CompletedTask; // TODO record this happened so we can verify it.
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
=> Task.CompletedTask; // TODO record this happened so we can verify it.
}
public struct TestPixelForAgnosticDecode : IPixel<TestPixelForAgnosticDecode>

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

@ -158,7 +158,7 @@ public class ImagingTestCaseUtility
public string SaveTestOutputFile(
Image image,
string extension = null,
ImageEncoder encoder = null,
IImageEncoder encoder = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
@ -203,7 +203,7 @@ public class ImagingTestCaseUtility
public string[] SaveTestOutputFileMultiFrame<TPixel>(
Image<TPixel> image,
string extension = "png",
ImageEncoder encoder = null,
IImageEncoder encoder = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
where TPixel : unmanaged, IPixel<TPixel>

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
@ -13,38 +12,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
/// </summary>
public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder
{
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
using PngEncoderCore encoder = new(allocator, configuration, this);
encoder.Encode(image, stream);
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public override async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
// The introduction of a local variable that refers to an object the implements
// 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);
encoder.Encode(image, stream, cancellationToken);
}
}

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

@ -3,10 +3,11 @@
using System.Drawing.Imaging;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
public class SystemDrawingReferenceEncoder : ImageEncoder
public class SystemDrawingReferenceEncoder : IImageEncoder
{
private readonly ImageFormat imageFormat;
@ -17,13 +18,17 @@ public class SystemDrawingReferenceEncoder : ImageEncoder
public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp);
public override void Encode<TPixel>(Image<TPixel> image, Stream stream)
public bool SkipMetadata { get; init; }
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image);
sdBitmap.Save(stream, this.imageFormat);
}
public override Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image))
{

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

@ -26,7 +26,7 @@ public static partial class TestEnvironment
return Configuration.ImageFormatsManager.FindDecoder(format);
}
internal static ImageEncoder GetReferenceEncoder(string filePath)
internal static IImageEncoder GetReferenceEncoder(string filePath)
{
IImageFormat format = GetImageFormat(filePath);
return Configuration.ImageFormatsManager.FindEncoder(format);
@ -43,7 +43,7 @@ public static partial class TestEnvironment
this Configuration cfg,
IImageFormat imageFormat,
IImageDecoder decoder,
ImageEncoder encoder,
IImageEncoder encoder,
IImageFormatDetector detector)
{
cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder);
@ -61,8 +61,8 @@ public static partial class TestEnvironment
new WebpConfigurationModule(),
new TiffConfigurationModule());
ImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration();
ImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration();
IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
// Magick codecs should work on all platforms
cfg.ConfigureCodecs(

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

@ -30,7 +30,7 @@ public static class TestImageExtensions
string extension = "png",
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true,
ImageEncoder encoder = null)
IImageEncoder encoder = null)
=> image.DebugSave(
provider,
(object)testOutputDetails,
@ -57,7 +57,7 @@ public static class TestImageExtensions
string extension = "png",
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true,
ImageEncoder encoder = null)
IImageEncoder encoder = null)
{
if (TestEnvironment.RunsWithCodeCoverage)
{
@ -77,7 +77,7 @@ public static class TestImageExtensions
public static void DebugSave(
this Image image,
ITestImageProvider provider,
ImageEncoder encoder,
IImageEncoder encoder,
FormattableString testOutputDetails,
bool appendPixelTypeToFileName = true)
=> image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName);
@ -93,7 +93,7 @@ public static class TestImageExtensions
public static void DebugSave(
this Image image,
ITestImageProvider provider,
ImageEncoder encoder,
IImageEncoder encoder,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
=> provider.Utility.SaveTestOutputFile(
@ -664,7 +664,7 @@ public static class TestImageExtensions
ITestImageProvider provider,
string extension,
object testOutputDetails,
ImageEncoder encoder,
IImageEncoder encoder,
ImageComparer customComparer = null,
bool appendPixelTypeToFileName = true,
string referenceImageExtension = null,

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

@ -62,7 +62,7 @@ public class TestEnvironmentTests
return;
}
ImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName);
IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName);
Assert.IsType(expectedEncoderType, encoder);
}
@ -96,7 +96,7 @@ public class TestEnvironmentTests
return;
}
ImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName);
IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName);
Assert.IsType(expectedEncoderType, encoder);
}

Loading…
Cancel
Save