From c550af8c5ad820a7a46bfd76ddfed067ef1227d5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 15 Nov 2022 22:08:48 +1000 Subject: [PATCH] Re-introduce IImageEncoder and split encoding pipelines. --- .../Advanced/AdvancedImageExtensions.cs | 8 +- src/ImageSharp/Advanced/AotCompilerTools.cs | 6 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 11 +-- src/ImageSharp/Formats/Gif/GifEncoder.cs | 11 +-- .../{ImageEncoder.cs => IImageEncoder.cs} | 26 +---- .../Formats/IImageEncoderInternals.cs | 4 +- src/ImageSharp/Formats/ImageDecoder.cs | 96 +++++++++---------- .../Formats/ImageEncoderUtilities.cs | 56 ----------- src/ImageSharp/Formats/ImageFormatManager.cs | 16 ++-- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 13 +-- src/ImageSharp/Formats/Pbm/PbmEncoder.cs | 15 +-- src/ImageSharp/Formats/Png/PngEncoder.cs | 14 +-- .../Formats/QuantizingImageEncoder.cs | 23 +++++ .../Formats/SynchronousImageEncoder.cs | 94 ++++++++++++++++++ src/ImageSharp/Formats/Tga/TgaEncoder.cs | 13 +-- src/ImageSharp/Formats/Tiff/TiffEncoder.cs | 11 +-- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 13 +-- src/ImageSharp/Image.cs | 8 +- src/ImageSharp/ImageExtensions.cs | 18 ++-- .../Formats/ImageFormatManagerTests.cs | 10 +- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 8 +- .../ImageSharp.Tests/Image/ImageTests.Save.cs | 2 +- .../Image/ImageTests.SaveAsync.cs | 4 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- .../Image/LargeImageIntegrationTests.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 2 +- tests/ImageSharp.Tests/TestFormat.cs | 12 ++- .../TestUtilities/ImagingTestCaseUtility.cs | 4 +- ...SharpPngEncoderWithDefaultConfiguration.cs | 32 +------ .../SystemDrawingReferenceEncoder.cs | 11 ++- .../TestUtilities/TestEnvironment.Formats.cs | 8 +- .../TestUtilities/TestImageExtensions.cs | 10 +- .../Tests/TestEnvironmentTests.cs | 4 +- 33 files changed, 266 insertions(+), 301 deletions(-) rename src/ImageSharp/Formats/{ImageEncoder.cs => IImageEncoder.cs} (58%) delete mode 100644 src/ImageSharp/Formats/ImageEncoderUtilities.cs create mode 100644 src/ImageSharp/Formats/QuantizingImageEncoder.cs create mode 100644 src/ImageSharp/Formats/SynchronousImageEncoder.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index f7fb0948fc..10267c8ef7 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -21,8 +21,8 @@ public static class AdvancedImageExtensions /// The target file path to save the image to. /// The file path is null. /// No encoder available for provided path. - /// The matching . - public static ImageEncoder DetectEncoder(this Image source, string filePath) + /// The matching . + 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 enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); } diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 00f7b1c3b5..a0f70e640c 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -230,7 +230,7 @@ internal static class AotCompilerTools } /// - /// This method pre-seeds the all in the AoT compiler. + /// This method pre-seeds the all in the AoT compiler. /// /// The pixel format. [Preserve] @@ -266,14 +266,14 @@ internal static class AotCompilerTools } /// - /// This method pre-seeds the in the AoT compiler. + /// This method pre-seeds the in the AoT compiler. /// /// The pixel format. /// The encoder. [Preserve] private static void AotCompileImageEncoder() where TPixel : unmanaged, IPixel - where TEncoder : ImageEncoder + where TEncoder : IImageEncoder { default(TEncoder).Encode(default, default); default(TEncoder).EncodeAsync(default, default, default); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 9e20da170a..156e2f9610 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -24,16 +24,9 @@ public sealed class BmpEncoder : QuantizingImageEncoder public bool SupportTransparency { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { BmpEncoderCore encoder = new(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - BmpEncoderCore encoder = new(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 351554eb07..386b1bd1c3 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -16,16 +16,9 @@ public sealed class GifEncoder : QuantizingImageEncoder public GifColorTableMode? ColorTableMode { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { GifEncoderCore encoder = new(image.GetConfiguration(), this); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - GifEncoderCore encoder = new(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs similarity index 58% rename from src/ImageSharp/Formats/ImageEncoder.cs rename to src/ImageSharp/Formats/IImageEncoder.cs index c887e7b0b2..42d04a54dc 100644 --- a/src/ImageSharp/Formats/ImageEncoder.cs +++ b/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; /// -/// The base class for all image encoders. +/// Defines the contract for all image encoders. /// -public abstract class ImageEncoder +public interface IImageEncoder { /// /// Gets a value indicating whether to ignore decoded metadata when encoding. @@ -23,7 +21,7 @@ public abstract class ImageEncoder /// The pixel format. /// The to encode from. /// The to encode the image data to. - public abstract void Encode(Image image, Stream stream) + public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel; /// @@ -34,22 +32,6 @@ public abstract class ImageEncoder /// The to encode the image data to. /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. - public abstract Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; } - -/// -/// The base class for all image encoders that allow color palette generation via quantization. -/// -public abstract class QuantizingImageEncoder : ImageEncoder -{ - /// - /// Gets the quantizer used to generate the color palette. - /// - public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree; - - /// - /// Gets the used for quantization when building color palettes. - /// - public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); -} diff --git a/src/ImageSharp/Formats/IImageEncoderInternals.cs b/src/ImageSharp/Formats/IImageEncoderInternals.cs index ca9c474e13..131949c968 100644 --- a/src/ImageSharp/Formats/IImageEncoderInternals.cs +++ b/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; /// -/// Abstraction for shared internals for ***DecoderCore implementations to be used with . +/// Abstraction for shared internals for ***DecoderCore implementations. /// internal interface IImageEncoderInternals { diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs index 42d476e925..b9bcd97e9c 100644 --- a/src/ImageSharp/Formats/ImageDecoder.cs +++ b/src/ImageSharp/Formats/ImageDecoder.cs @@ -13,6 +13,53 @@ namespace SixLabors.ImageSharp.Formats; /// public abstract class ImageDecoder : IImageDecoder { + /// + public Image Decode(DecoderOptions options, Stream stream) + where TPixel : unmanaged, IPixel + => WithSeekableStream( + options, + stream, + s => this.Decode(options, s, default)); + + /// + public Image Decode(DecoderOptions options, Stream stream) + => WithSeekableStream( + options, + stream, + s => this.Decode(options, s, default)); + + /// + public Task> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => WithSeekableStreamAsync( + options, + stream, + (s, ct) => this.Decode(options, s, ct), + cancellationToken); + + /// + public Task DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => WithSeekableStreamAsync( + options, + stream, + (s, ct) => this.Decode(options, s, ct), + cancellationToken); + + /// + public IImageInfo Identify(DecoderOptions options, Stream stream) + => WithSeekableStream( + options, + stream, + s => this.Identify(options, s, default)); + + /// + public Task IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => WithSeekableStreamAsync( + options, + stream, + (s, ct) => this.Identify(options, s, ct), + cancellationToken); + /// /// Decodes the image from the specified stream to an of a specific pixel type. /// @@ -93,53 +140,6 @@ public abstract class ImageDecoder : IImageDecoder return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; } - /// - public Image Decode(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - => WithSeekableStream( - options, - stream, - s => this.Decode(options, s, default)); - - /// - public Image Decode(DecoderOptions options, Stream stream) - => WithSeekableStream( - options, - stream, - s => this.Decode(options, s, default)); - - /// - public Task> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync( - options, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken); - - /// - public Task DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => WithSeekableStreamAsync( - options, - stream, - (s, ct) => this.Decode(options, s, ct), - cancellationToken); - - /// - public IImageInfo Identify(DecoderOptions options, Stream stream) - => WithSeekableStream( - options, - stream, - s => this.Identify(options, s, default)); - - /// - public Task IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => WithSeekableStreamAsync( - options, - stream, - (s, ct) => this.Identify(options, s, ct), - cancellationToken); - internal static T WithSeekableStream( 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); diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs deleted file mode 100644 index 665431c952..0000000000 --- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs +++ /dev/null @@ -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( - this IImageEncoderInternals encoder, - Image image, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - 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( - this IImageEncoderInternals encoder, - Image image, - Stream stream) - where TPixel : unmanaged, IPixel - => encoder.Encode(image, stream, default); -} diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index 23229703c0..b13f3e8d8d 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -17,9 +17,9 @@ public class ImageFormatManager private static readonly object HashLock = new(); /// - /// The list of supported keyed to mime types. + /// The list of supported keyed to mime types. /// - private readonly ConcurrentDictionary mimeTypeEncoders = new(); + private readonly ConcurrentDictionary mimeTypeEncoders = new(); /// /// The list of supported keyed to mime types. @@ -64,9 +64,9 @@ public class ImageFormatManager internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; /// - /// Gets the currently registered s. + /// Gets the currently registered s. /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; /// /// Registers a new format provider. @@ -117,7 +117,7 @@ public class ImageFormatManager /// /// The image format to register the encoder for. /// The encoder to use, - 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. /// /// The format to discover - /// The if found otherwise null - public ImageEncoder FindEncoder(IImageFormat format) + /// The if found otherwise null + 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; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 2ddd829f87..63f477ffc6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg; /// /// Encoder for writing the data image to a stream in jpeg format. /// -public sealed class JpegEncoder : ImageEncoder +public sealed class JpegEncoder : SynchronousImageEncoder { /// /// Backing field for . @@ -48,16 +48,9 @@ public sealed class JpegEncoder : ImageEncoder public JpegEncodingColor? ColorType { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { JpegEncoderCore encoder = new(this); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - JpegEncoderCore encoder = new(this); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs index 6b25347c04..b812e76480 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm; /// /// The specification of these images is found at . /// -public sealed class PbmEncoder : ImageEncoder +public sealed class PbmEncoder : SynchronousImageEncoder { /// /// Gets the encoding of the pixels. @@ -42,21 +42,14 @@ public sealed class PbmEncoder : ImageEncoder public PbmColorType? ColorType { get; init; } /// - /// Gets the Data Type of the pixel components. + /// Gets the data type of the pixel components. /// public PbmComponentType? ComponentType { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { PbmEncoderCore encoder = new(image.GetConfiguration(), this); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - PbmEncoderCore encoder = new(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 2daa136c3d..29562d2f8f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -74,19 +74,9 @@ public class PngEncoder : QuantizingImageEncoder public PngTransparentColorMode TransparentColorMode { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { using PngEncoderCore encoder = new(image.GetMemoryAllocator(), image.GetConfiguration(), this); - encoder.Encode(image, stream); - } - - /// - public override async Task EncodeAsync(Image 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); } } diff --git a/src/ImageSharp/Formats/QuantizingImageEncoder.cs b/src/ImageSharp/Formats/QuantizingImageEncoder.cs new file mode 100644 index 0000000000..a885e5550b --- /dev/null +++ b/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; + +/// +/// Acts as a base class for all image encoders that allow color palette generation via quantization. +/// +public abstract class QuantizingImageEncoder : SynchronousImageEncoder +{ + /// + /// Gets the quantizer used to generate the color palette. + /// + public IQuantizer Quantizer { get; init; } = KnownQuantizers.Octree; + + /// + /// Gets the used for quantization when building color palettes. + /// + public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy(); +} diff --git a/src/ImageSharp/Formats/SynchronousImageEncoder.cs b/src/ImageSharp/Formats/SynchronousImageEncoder.cs new file mode 100644 index 0000000000..741a206df2 --- /dev/null +++ b/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; + +/// +/// Acts as a base class for image encoders. +/// Types that inherit this encoder are required to implement cancellable synchronous encoding operations only. +/// +public abstract class SynchronousImageEncoder : IImageEncoder +{ + /// + public bool SkipMetadata { get; init; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + => this.EncodeWithSeekableStream(image, stream, default); + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.EncodeWithSeekableStreamAsync(image, stream, cancellationToken); + + /// + /// Encodes the image to the specified stream from the . + /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + protected abstract void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + private void EncodeWithSeekableStream(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + 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(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + 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); + } + } + } +} diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index e0a3932355..da22935812 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Tga; /// /// Image encoder for writing an image to a stream as a targa truevision image. /// -public sealed class TgaEncoder : ImageEncoder +public sealed class TgaEncoder : SynchronousImageEncoder { /// /// Gets the number of bits per pixel. @@ -21,16 +21,9 @@ public sealed class TgaEncoder : ImageEncoder public TgaCompression Compression { get; init; } = TgaCompression.RunLength; /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { TgaEncoderCore encoder = new(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - TgaEncoderCore encoder = new(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index e7bb08cdc3..24cca41dc2 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -40,16 +40,9 @@ public class TiffEncoder : QuantizingImageEncoder public TiffPredictor? HorizontalPredictor { get; init; } /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { TiffEncoderCore encode = new(this, image.GetMemoryAllocator()); - encode.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - TiffEncoderCore encoder = new(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); + encode.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 359128254f..650afc4dd7 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Image encoder for writing an image to a stream in the Webp format. /// -public sealed class WebpEncoder : ImageEncoder +public sealed class WebpEncoder : SynchronousImageEncoder { /// /// 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; /// - public override void Encode(Image image, Stream stream) + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { WebpEncoderCore encoder = new(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } - - /// - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - { - WebpEncoderCore encoder = new(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); + encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 7094bd047c..2df86b1097 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -101,7 +101,7 @@ public abstract partial class Image : IImage, IConfigurationProvider /// The stream to save the image to. /// The encoder to save the image with. /// Thrown if the stream or encoder is null. - 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 /// The token to monitor for cancellation requests. /// Thrown if the stream or encoder is null. /// A representing the asynchronous operation. - 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; diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index d12c483450..2174a0eb94 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -43,14 +43,12 @@ public static partial class ImageExtensions /// The encoder to save the image with. /// The path is null. /// The encoder is null. - 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); } /// @@ -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 val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + foreach (KeyValuePair 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 val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index cb2611c405..cd1180a9bf 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -56,7 +56,7 @@ public class ImageFormatManagerTests [Fact] public void RegisterNullMimeTypeEncoder() { - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, new Mock().Object)); + Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, new Mock().Object)); Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null)); Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, null)); } @@ -72,14 +72,14 @@ public class ImageFormatManagerTests [Fact] public void RegisterMimeTypeEncoderReplacesLast() { - ImageEncoder encoder1 = new Mock().Object; + IImageEncoder encoder1 = new Mock().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().Object; + IImageEncoder encoder2 = new Mock().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); } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index e59f4337a4..a3f03bed5a 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -16,8 +16,8 @@ public class ImageSaveTests : IDisposable { private readonly Image image; private readonly Mock fileSystem; - private readonly Mock encoder; - private readonly Mock encoderNotInFormat; + private readonly Mock encoder; + private readonly Mock encoderNotInFormat; private IImageFormatDetector localMimeTypeDetector; private Mock 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(); + this.encoder = new Mock(); - this.encoderNotInFormat = new Mock(); + this.encoderNotInFormat = new Mock(); this.fileSystem = new Mock(); var config = new Configuration diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index 576af74fa5..88a6b5890b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -68,7 +68,7 @@ public partial class ImageTests { using var image = new Image(5, 5); image.Dispose(); - ImageEncoder encoder = Mock.Of(); + IImageEncoder encoder = Mock.Of(); using (var stream = new MemoryStream()) { Assert.Throws(() => image.Save(stream, encoder)); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index ec16225744..1ceadb964d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -102,7 +102,7 @@ public partial class ImageTests { var image = new Image(5, 5); image.Dispose(); - ImageEncoder encoder = Mock.Of(); + IImageEncoder encoder = Mock.Of(); using (var stream = new MemoryStream()) { await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); @@ -120,7 +120,7 @@ public partial class ImageTests { using (var image = new Image(5, 5)) { - ImageEncoder encoder = image.DetectEncoder(filename); + IImageEncoder encoder = image.DetectEncoder(filename); using (var stream = new MemoryStream()) { var asyncStream = new AsyncStreamWrapper(stream, () => false); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index def1403fda..02ccfb713b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -328,7 +328,7 @@ public partial class ImageTests public void KnownExtension_ReturnsEncoder() { using var image = new Image(1, 1); - ImageEncoder encoder = image.DetectEncoder("dummy.png"); + IImageEncoder encoder = image.DetectEncoder("dummy.png"); Assert.NotNull(encoder); Assert.IsType(encoder); } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 2bbf12bdf8..b65719228c 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/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}"); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 092698cb26..8ef31a2af2 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -242,7 +242,7 @@ public class XmpProfileTests return profile; } - private static Image WriteAndRead(Image image, ImageEncoder encoder) + private static Image WriteAndRead(Image image, IImageEncoder encoder) { using (var memStream = new MemoryStream()) { diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index eaf0a5df9c..59eafe17d6 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/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 FileExtensions => this.testFormat.SupportedExtensions; - public override void Encode(Image image, Stream stream) + public bool SkipMetadata { get; init; } + + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { // TODO record this happened so we can verify it. } - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - => Task.CompletedTask; // TODO record this happened so we can verify it. + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => Task.CompletedTask; // TODO record this happened so we can verify it. } public struct TestPixelForAgnosticDecode : IPixel diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index c4f26b0f61..460ecac85a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/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( Image image, string extension = "png", - ImageEncoder encoder = null, + IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) where TPixel : unmanaged, IPixel diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs index 8d7b5042f2..a4d305d97f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs +++ b/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; /// public sealed class ImageSharpPngEncoderWithDefaultConfiguration : PngEncoder { - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public override void Encode(Image image, Stream stream) + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { Configuration configuration = Configuration.Default; MemoryAllocator allocator = configuration.MemoryAllocator; using PngEncoderCore encoder = new(allocator, configuration, this); - encoder.Encode(image, stream); - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public override async Task EncodeAsync(Image 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); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 954ec2ffae..d8dda2eea8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/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(Image image, Stream stream) + public bool SkipMetadata { get; init; } + + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); sdBitmap.Save(stream, this.imageFormat); } - public override Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 41b6259595..68c1664a8f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/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( diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 8843204763..31c9f541ea 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/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, diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index b879ec649f..36c078dc0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/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); }