From 0fb5d9c79fc9cdda89bb29a58f50c81b598e979a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 10 Jun 2020 17:00:05 +0200 Subject: [PATCH 01/33] hax --- src/ImageSharp/ImageSharp.csproj | 6 ++++-- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 64a496141..cd5026117 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,13 +10,15 @@ $(packageversion) 0.0.1 - netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + + netcoreapp3.1 true true SixLabors.ImageSharp Image Resize Crop Gif Jpg Jpeg Bitmap Png Core SixLabors.ImageSharp + SA1636 @@ -41,7 +43,7 @@ - + True True diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 98f8e9574..f1c3aa28e 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,7 +2,8 @@ - netcoreapp3.1;netcoreapp2.1;net472 + + netcoreapp3.1 True True SixLabors.ImageSharp.Tests From f056e51cf7829990265658423c67fc6d3f3f29ca Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 22 Jul 2020 01:25:30 +0200 Subject: [PATCH 02/33] add cancellable overloads --- src/ImageSharp/Image.Decode.cs | 9 +- src/ImageSharp/Image.FromFile.cs | 48 +++++-- src/ImageSharp/Image.FromStream.cs | 194 ++++++++++++++++++++++++++--- 3 files changed, 222 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 683590fd1..bc44cd8ca 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -156,9 +157,10 @@ namespace SixLabors.ImageSharp /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// The pixel format. /// A representing the asynchronous operation. - private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config) + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); @@ -183,7 +185,7 @@ namespace SixLabors.ImageSharp return (img, format); } - private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config) + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) { (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); if (decoder is null) @@ -221,11 +223,12 @@ namespace SixLabors.ImageSharp /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// /// A representing the asynchronous operation with the /// property of the returned type set to null if a suitable detector /// is not found. - private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config) + private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentityAsync(Stream stream, Configuration config, CancellationToken cancellationToken) { (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index a078f2db9..7dc06b3ca 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -136,13 +137,26 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static async Task LoadAsync(Configuration configuration, string path) + public static Task LoadAsync(Configuration configuration, string path) + => LoadAsync(configuration, path, default(CancellationToken)); + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration for the decoder. + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync(Configuration configuration, string path, CancellationToken cancellationToken) { - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - (Image img, _) = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); - return img; - } + using Stream stream = configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return img; } /// @@ -181,14 +195,28 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder) + => LoadAsync(configuration, path, decoder, default); + + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder, CancellationToken cancellationToken) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(path, nameof(path)); - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return LoadAsync(configuration, stream, decoder); - } + using Stream stream = configuration.FileSystem.OpenRead(path); + return LoadAsync(configuration, stream, decoder, cancellationToken); } /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index beec0b188..d005873ca 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -62,7 +63,8 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - s => InternalDetectFormatAsync(s, configuration)); + (s, _) => InternalDetectFormatAsync(s, configuration), + default); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -192,7 +194,29 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - s => InternalIdentityAsync(s, configuration ?? Configuration.Default)); + (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), + default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The representing the asyncronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => WithSeekableStreamAsync( + configuration, + stream, + (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), + cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -310,12 +334,31 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + => LoadAsync(configuration, stream, decoder, default); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The configuration for the decoder. + /// The stream containing image information. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The decoder is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder, CancellationToken cancellationToken) { Guard.NotNull(decoder, nameof(decoder)); return WithSeekableStreamAsync( configuration, stream, - s => decoder.DecodeAsync(configuration, s)); + (s, ct) => decoder.DecodeAsync(configuration, s), + cancellationToken); } /// @@ -348,6 +391,25 @@ namespace SixLabors.ImageSharp return fmt.Image; } + /// + /// Decode a new instance of the class from the given stream. + /// + /// The configuration for the decoder. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return fmt.Image; + } + /// /// Create a new instance of the class from the given stream. /// @@ -432,11 +494,28 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A representing the asynchronous operation. public static Task> LoadAsync(Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(stream, decoder, default); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( Configuration.Default, stream, - s => decoder.DecodeAsync(Configuration.Default, s)); + (s, ct) => decoder.DecodeAsync(Configuration.Default, s), + cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -468,12 +547,38 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) + public static Task> LoadAsync( + Configuration configuration, + Stream stream, + IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(configuration, stream, decoder, default); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The Configuration. + /// The stream containing image information. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync( + Configuration configuration, + Stream stream, + IImageDecoder decoder, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( configuration, stream, - s => decoder.DecodeAsync(configuration, s)); + (s, ct) => decoder.DecodeAsync(configuration, s), + cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -538,12 +643,30 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream) + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, + Stream stream) + => LoadWithFormatAsync(configuration, stream, default); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, - async s => await DecodeAsync(s, configuration).ConfigureAwait(false)) + async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false), + cancellationToken) .ConfigureAwait(false); if (data.Image != null) @@ -574,14 +697,33 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream) + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => LoadWithFormatAsync(configuration, stream, default); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, - s => DecodeAsync(s, configuration)) + (s, ct) => DecodeAsync(s, configuration, ct), + cancellationToken) .ConfigureAwait(false); if (data.Image != null) @@ -612,10 +754,28 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task> LoadAsync(Configuration configuration, Stream stream) + public static Task> LoadAsync(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => LoadAsync(configuration, stream, default(CancellationToken)); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - (Image img, _) = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); return img; } @@ -700,11 +860,13 @@ namespace SixLabors.ImageSharp /// The configuration. /// The input stream. /// The action to perform. + /// The cancellation token. /// The . private static async Task WithSeekableStreamAsync( Configuration configuration, Stream stream, - Func> action) + Func> action, + CancellationToken cancellationToken) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(stream, nameof(stream)); @@ -725,14 +887,14 @@ namespace SixLabors.ImageSharp stream.Position = 0; } - return await action(stream).ConfigureAwait(false); + return await action(stream, cancellationToken).ConfigureAwait(false); } using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - return await action(memoryStream).ConfigureAwait(false); + return await action(memoryStream, cancellationToken).ConfigureAwait(false); } } } From 9c465a7ff2620618e5e52ce157bbd109d5ea8ee1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 22 Jul 2020 03:09:26 +0200 Subject: [PATCH 03/33] add asynchronous decoder logic --- .../InvalidImageContentException.cs | 6 + src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 47 ++---- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 5 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 46 ++---- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 5 +- src/ImageSharp/Formats/IImageDecoder.cs | 7 +- .../Formats/IImageDecoderInternals.cs | 20 ++- src/ImageSharp/Formats/IImageInfoDetector.cs | 4 +- .../Formats/ImageDecoderUtilities.cs | 146 +++++++++++++++++- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 47 ++---- .../Formats/Jpeg/JpegDecoderCore.cs | 8 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 46 ++---- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 +- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 47 ++---- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 5 +- src/ImageSharp/Image.Decode.cs | 10 +- src/ImageSharp/Image.FromStream.cs | 6 +- src/ImageSharp/ImageSharp.csproj | 2 +- .../Image/ImageTests.ImageLoadTestBase.cs | 2 +- tests/ImageSharp.Tests/TestFormat.cs | 7 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 7 +- .../SystemDrawingReferenceDecoder.cs | 10 +- .../Tests/TestImageProviderTests.cs | 13 +- 23 files changed, 279 insertions(+), 222 deletions(-) diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs index 3b8641532..e4713e237 100644 --- a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs +++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp { @@ -32,5 +33,10 @@ namespace SixLabors.ImageSharp : base(errorMessage, innerException) { } + + internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException) + : this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException) + { + } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 7e8ac0721..e057db150 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -36,18 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); - } + return decoder.Decode(stream, CreateLargeImageException); } /// @@ -55,46 +45,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); - } + return decoder.DecodeAsync(stream, CreateLargeImageException, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new BmpDecoderCore(configuration, this).Identify(bufferedStream); + return new BmpDecoderCore(configuration, this).Identify(stream, CreateLargeImageException); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream); + return new BmpDecoderCore(configuration, this).IdentifyAsync(stream, CreateLargeImageException, cancellationToken); + } + + private static InvalidImageContentException CreateLargeImageException(InvalidMemoryOperationException ex, Size dims) + { + return new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index ea8fd11a8..6f9223637 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { try @@ -197,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadImageHeaders(stream, out _, out _); return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 2a5fde6ac..206dd6f2f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -30,21 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -52,30 +39,17 @@ namespace SixLabors.ImageSharp.Formats.Gif => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -85,18 +59,18 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); using var bufferedStream = new BufferedReadStream(stream); - return decoder.Identify(bufferedStream); + return decoder.Identify(bufferedStream, default); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); using var bufferedStream = new BufferedReadStream(stream); - return decoder.IdentifyAsync(bufferedStream); + return await decoder.IdentifyAsync(bufferedStream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 78ffee8bd..8f5cc3b5c 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Image image = null; @@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { try { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 97886e526..b55f1119b 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; @@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Formats /// The pixel format. /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task> DecodeAsync(Configuration configuration, Stream stream) + Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// @@ -48,8 +50,9 @@ namespace SixLabors.ImageSharp.Formats /// /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task DecodeAsync(Configuration configuration, Stream stream); + Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs index 33748bf24..e190f7add 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; @@ -17,21 +18,36 @@ namespace SixLabors.ImageSharp.Formats /// Configuration Configuration { get; } + /// + /// Gets the dimensions of the image being decoded. + /// + Size Dimensions { get; } + /// /// Decodes the image from the specified stream. /// /// The pixel format. /// The stream, where the image should be decoded from. Cannot be null. + /// The token to monitor for cancellation requests. /// is null. /// The decoded image. - Image Decode(BufferedReadStream stream) + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// /// Reads the raw image information from the specified stream. /// /// The containing image data. + /// The token to monitor for cancellation requests. /// The . - IImageInfo Identify(BufferedReadStream stream); + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 862c64999..6f5fc2333 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats @@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats /// /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The object - Task IdentifyAsync(Configuration configuration, Stream stream); + Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 9d1639a09..a7ade5e24 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -14,21 +17,152 @@ namespace SixLabors.ImageSharp.Formats /// Reads the raw image information from the specified stream. /// /// The decoder. - /// The containing image data. + /// The containing image data. + /// The token to monitor for cancellation requests. /// is null. /// A representing the asynchronous operation. - public static Task IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) - => Task.FromResult(decoder.Identify(stream)); + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Stream stream, + CancellationToken cancellationToken) + => decoder.IdentifyAsync(stream, DefaultLargeImageExceptionFactory, cancellationToken); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// is null. + /// A representing the asynchronous operation. + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Stream stream, + Func tooLargeImageExceptionFactory, + CancellationToken cancellationToken) + { + try + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); + return Task.FromResult(imageInfo); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } /// /// Decodes the image from the specified stream. /// /// The pixel format. /// The decoder. - /// The containing image data. + /// The containing image data. + /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. - public static Task> DecodeAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => + decoder.DecodeAsync( + stream, + DefaultLargeImageExceptionFactory, + cancellationToken); + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The decoder. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => Task.FromResult(decoder.Decode(stream)); + { + try + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + Image image = decoder.Decode(bufferedReadStream, cancellationToken); + return Task.FromResult(image); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException>(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled>(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException>(ex); + } + } + + public static IImageInfo Identify(this IImageDecoderInternals decoder, Stream stream) + => decoder.Identify(stream, DefaultLargeImageExceptionFactory); + + public static IImageInfo Identify( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory) + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + + try + { + return decoder.Identify(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); + } + } + + public static Image Decode(this IImageDecoderInternals decoder, Stream stream) + where TPixel : unmanaged, IPixel + => decoder.Decode(stream, DefaultLargeImageExceptionFactory); + + public static Image Decode( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory) + where TPixel : unmanaged, IPixel + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + + try + { + return decoder.Decode(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); + } + } + + private static InvalidImageContentException DefaultLargeImageExceptionFactory( + InvalidMemoryOperationException memoryOperationException, + Size dimensions) => + new InvalidImageContentException(dimensions, memoryOperationException); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index c5332acb5..106c1f597 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -24,20 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); - - JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -45,31 +33,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); - - JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -77,20 +52,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - - return decoder.Identify(bufferedStream); + return decoder.Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - - return decoder.IdentifyAsync(bufferedStream); + return decoder.IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2956d2c11..6874d09e9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -117,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Size ImageSizeInPixels { get; private set; } + /// + Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels; + /// /// Gets the number of MCU blocks in the image as . /// @@ -205,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { this.ParseStream(stream); @@ -217,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ParseStream(stream, true); this.InitExifProfile(); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 9eb927784..677a2003e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -22,65 +23,36 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream) { var decoder = new PngDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Identify(bufferedStream); + return decoder.Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { var decoder = new PngDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - return decoder.IdentifyAsync(bufferedStream); + return decoder.IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 89fa4e63d..b751a704a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,6 +9,7 @@ using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Png public Size Dimensions => new Size(this.header.Width, this.header.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); @@ -224,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { var metadata = new ImageMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata(); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 3d9b9a3d2..bcf52a011 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -21,21 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -43,49 +30,33 @@ namespace SixLabors.ImageSharp.Formats.Tga => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new TgaDecoderCore(configuration, this).Identify(bufferedStream); + return new TgaDecoderCore(configuration, this).Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream); + return new TgaDecoderCore(configuration, this).IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 7cd83fedb..eef6e7362 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tga public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { try @@ -640,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadFileHeader(stream); return new ImageInfo( diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index bc44cd8ca..ff4886a10 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -163,13 +163,15 @@ namespace SixLabors.ImageSharp private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); + (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) + .ConfigureAwait(false); if (decoder is null) { return (null, null); } - Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); + Image img = await decoder.DecodeAsync(config, stream, cancellationToken) + .ConfigureAwait(false); return (img, format); } @@ -193,7 +195,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); + Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false); return (img, format); } @@ -242,7 +244,7 @@ namespace SixLabors.ImageSharp return (null, format); } - IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false); + IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false); return (info, format); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index d005873ca..95f775d84 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp return WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s), + (s, ct) => decoder.DecodeAsync(configuration, s, ct), cancellationToken); } @@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( Configuration.Default, stream, - (s, ct) => decoder.DecodeAsync(Configuration.Default, s), + (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct), cancellationToken); /// @@ -577,7 +577,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s), + (s, ct) => decoder.DecodeAsync(configuration, s, ct), cancellationToken); /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index cd5026117..97a2a332d 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 70d572d60..8c9f0994f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); - detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); + detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), TODO)).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index a883039f2..4c62c101c 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -219,7 +220,8 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public Task> DecodeAsync(Configuration config, Stream stream) + public Task> DecodeAsync(Configuration config, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(config, stream)); @@ -227,7 +229,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } public class TestEncoder : ImageSharp.Formats.IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index fae3ff5a5..f641c579e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using SixLabors.ImageSharp.Formats; @@ -46,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -82,6 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index ae6e2e3f1..62318bd86 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; @@ -14,7 +15,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -48,7 +50,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => Task.FromResult(this.Identify(configuration, stream)); public IImageInfo Identify(Configuration configuration, Stream stream) @@ -62,6 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 978c0555c..979b57a63 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -371,7 +372,8 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -391,7 +393,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } private class TestDecoderWithParameters : IImageDecoder @@ -425,7 +428,8 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -445,7 +449,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } } From f0f6c305e312f8a82a9e0b1e293f73da695ecc17 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 22 Jul 2020 03:13:30 +0200 Subject: [PATCH 04/33] fix tests --- .../Image/ImageTests.ImageLoadTestBase.cs | 4 ++-- tests/ImageSharp.Tests/TestFormat.cs | 7 +++---- .../ReferenceCodecs/MagickReferenceDecoder.cs | 7 +++---- .../SystemDrawingReferenceDecoder.cs | 10 ++++------ .../TestUtilities/Tests/TestImageProviderTests.cs | 14 ++++++-------- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 8c9f0994f..ea1e6e0d7 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -3,7 +3,7 @@ using System; using System.IO; - +using System.Threading; using Moq; using SixLabors.ImageSharp.Formats; @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); - detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), TODO)).ReturnsAsync(this.localImageInfoMock.Object); + detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 4c62c101c..aa986aa8a 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -220,8 +220,7 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public Task> DecodeAsync(Configuration config, Stream stream, - CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration config, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(config, stream)); @@ -229,8 +228,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } public class TestEncoder : ImageSharp.Formats.IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index f641c579e..de8278a33 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -47,8 +47,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task> DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -84,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 62318bd86..1eb1328ef 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -15,8 +15,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Task> DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -50,8 +49,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task IdentifyAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => Task.FromResult(this.Identify(configuration, stream)); public IImageInfo Identify(Configuration configuration, Stream stream) @@ -65,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 979b57a63..129d17f4d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -372,8 +372,7 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -393,8 +392,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } private class TestDecoderWithParameters : IImageDecoder @@ -428,8 +427,7 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -449,8 +447,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream, - CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); } } } From b827f283826c88d617de947c08586a4ed5dd6928 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 22 Jul 2020 04:28:19 +0200 Subject: [PATCH 05/33] SemaphoreReadMemoryStream --- .../SemaphoreReadMemoryStream.cs | 71 ++++++++++++++ .../Tests/SemaphoreReadMemoryStreamTests.cs | 93 +++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs diff --git a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs new file mode 100644 index 000000000..296611bb5 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace SixLabors.ImageSharp.Tests.TestUtilities +{ + internal class SemaphoreReadMemoryStream : MemoryStream + { + private SemaphoreSlim waitSemaphore; + private readonly SemaphoreSlim signalFinishedSemaphore; + private readonly long waitAfterPosition; + + public SemaphoreReadMemoryStream(byte[] buffer, SemaphoreSlim waitSemaphore, SemaphoreSlim signalFinishedSemaphore, long waitAfterPosition) + : base(buffer) + { + this.waitSemaphore = waitSemaphore; + this.signalFinishedSemaphore = signalFinishedSemaphore; + this.waitAfterPosition = waitAfterPosition; + } + + public override int Read(byte[] buffer, int offset, int count) + { + int read = base.Read(buffer, offset, count); + if (this.Position + read > this.waitAfterPosition) + { + this.waitSemaphore.Wait(); + } + + this.SignalIfFinished(); + + return read; + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int read = await base.ReadAsync(buffer, offset, count, cancellationToken); + if (this.Position + read > this.waitAfterPosition) + { + await this.waitSemaphore.WaitAsync(); + } + + this.SignalIfFinished(); + + return read; + } + + public override int ReadByte() + { + if (this.Position + 1 > this.waitAfterPosition) + { + this.waitSemaphore.Wait(); + } + + int result = base.ReadByte(); + this.SignalIfFinished(); + return result; + } + + private void SignalIfFinished() + { + if (this.Position == this.Length) + { + this.signalFinishedSemaphore.Release(); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs new file mode 100644 index 000000000..ac90d5fb2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class SemaphoreReadMemoryStreamTests + { + private readonly SemaphoreSlim WaitSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim FinishedSemaphore = new SemaphoreSlim(0); + private readonly byte[] Buffer = new byte[128]; + + [Fact] + public void Read_BeforeWaitLimit_ShouldFinish() + { + using Stream stream = this.GetStream(); + int read = stream.Read(this.Buffer); + Assert.Equal(this.Buffer.Length, read); + } + + [Fact] + public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() + { + using Stream stream = this.GetStream(); + int read = await stream.ReadAsync(this.Buffer); + Assert.Equal(this.Buffer.Length, read); + } + + [Fact] + public async Task Read_AfterWaitLimit_ShouldPause() + { + using Stream stream = this.GetStream(); + stream.Read(this.Buffer); + + Task readTask = Task.Factory.StartNew(() => stream.Read(new byte[512]), TaskCreationOptions.LongRunning); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.WaitSemaphore.Release(); + await readTask; + } + + [Fact] + public async Task ReadAsync_AfterWaitLimit_ShouldPause() + { + using Stream stream = this.GetStream(); + await stream.ReadAsync(this.Buffer); + + Task readTask = + Task.Factory.StartNew(() => stream.ReadAsync(new byte[512]).AsTask(), TaskCreationOptions.LongRunning); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.WaitSemaphore.Release(); + await readTask; + } + + [Fact] + public async Task Read_WhenFinished_ShouldNotify() + { + using Stream stream = this.GetStream(512, int.MaxValue); + stream.Read(this.Buffer); + stream.Read(this.Buffer); + stream.Read(this.Buffer); + Assert.Equal(0, this.FinishedSemaphore.CurrentCount); + stream.Read(this.Buffer); + Assert.Equal(1, this.FinishedSemaphore.CurrentCount); + } + + [Fact] + public async Task ReadAsync_WhenFinished_ShouldNotify() + { + using Stream stream = this.GetStream(512, int.MaxValue); + await stream.ReadAsync(this.Buffer); + await stream.ReadAsync(this.Buffer); + await stream.ReadAsync(this.Buffer); + Assert.Equal(0, this.FinishedSemaphore.CurrentCount); + + Task lastRead = stream.ReadAsync(this.Buffer).AsTask(); + Task finishedTask = this.FinishedSemaphore.WaitAsync(); + await Task.WhenAll(lastRead, finishedTask); + } + + private Stream GetStream(int size = 1024, int waitAfterPosition = 256) + { + byte[] buffer = new byte[size]; + return new SemaphoreReadMemoryStream(buffer, this.WaitSemaphore, this.FinishedSemaphore, waitAfterPosition); + } + } +} From 803ebaa8ad8893fb0a89daaa81f7b37bacb8c08a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 25 Jul 2020 19:13:37 +0200 Subject: [PATCH 06/33] adapt #1286 --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 8 ++--- src/ImageSharp/Formats/Gif/GifDecoder.cs | 10 +++---- .../Formats/ImageDecoderUtilities.cs | 29 +++++++++++++------ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 8 ++--- src/ImageSharp/Formats/Png/PngDecoder.cs | 8 ++--- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 8 ++--- src/ImageSharp/Image.FromStream.cs | 2 +- 8 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index e057db150..fe831caf8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(stream, CreateLargeImageException); + return decoder.Decode(configuration, stream, CreateLargeImageException); } /// @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.DecodeAsync(stream, CreateLargeImageException, cancellationToken); + return decoder.DecodeAsync(configuration, stream, CreateLargeImageException, cancellationToken); } /// @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(stream, CreateLargeImageException); + return new BmpDecoderCore(configuration, this).Identify(configuration, stream, CreateLargeImageException); } /// @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).IdentifyAsync(stream, CreateLargeImageException, cancellationToken); + return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, CreateLargeImageException, cancellationToken); } private static InvalidImageContentException CreateLargeImageException(InvalidMemoryOperationException ex, Size dims) diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 206dd6f2f..e42f9c9f2 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.Decode(configuration, stream); } /// @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.DecodeAsync(stream, cancellationToken); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); + using var bufferedStream = new BufferedReadStream(configuration, stream); return decoder.Identify(bufferedStream, default); } @@ -69,8 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.IdentifyAsync(bufferedStream, cancellationToken); + using var bufferedStream = new BufferedReadStream(configuration, stream); + return await decoder.IdentifyAsync(configuration, bufferedStream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index a7ade5e24..5e4aeb9eb 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -17,20 +17,23 @@ namespace SixLabors.ImageSharp.Formats /// Reads the raw image information from the specified stream. /// /// The decoder. + /// /// The configuration for the image. /// The containing image data. /// The token to monitor for cancellation requests. /// is null. /// A representing the asynchronous operation. public static Task IdentifyAsync( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, CancellationToken cancellationToken) - => decoder.IdentifyAsync(stream, DefaultLargeImageExceptionFactory, cancellationToken); + => decoder.IdentifyAsync(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); /// /// Reads the raw image information from the specified stream. /// /// The decoder. + /// The configuration for the image. /// The containing image data. /// Factory method to handle as . /// The token to monitor for cancellation requests. @@ -38,13 +41,14 @@ namespace SixLabors.ImageSharp.Formats /// A representing the asynchronous operation. public static Task IdentifyAsync( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, Func tooLargeImageExceptionFactory, CancellationToken cancellationToken) { try { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); return Task.FromResult(imageInfo); } @@ -68,15 +72,18 @@ namespace SixLabors.ImageSharp.Formats /// /// The pixel format. /// The decoder. + /// The configuration for the image. /// The containing image data. /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. public static Task> DecodeAsync( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => decoder.DecodeAsync( + configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); @@ -86,12 +93,14 @@ namespace SixLabors.ImageSharp.Formats /// /// The pixel format. /// The decoder. + /// The configuration for the image. /// The containing image data. /// Factory method to handle as . /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. public static Task> DecodeAsync( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, Func largeImageExceptionFactory, CancellationToken cancellationToken) @@ -99,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats { try { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); Image image = decoder.Decode(bufferedReadStream, cancellationToken); return Task.FromResult(image); } @@ -118,15 +127,16 @@ namespace SixLabors.ImageSharp.Formats } } - public static IImageInfo Identify(this IImageDecoderInternals decoder, Stream stream) - => decoder.Identify(stream, DefaultLargeImageExceptionFactory); + public static IImageInfo Identify(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) + => decoder.Identify(configuration, stream, DefaultLargeImageExceptionFactory); public static IImageInfo Identify( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, Func largeImageExceptionFactory) { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); try { @@ -138,17 +148,18 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode(this IImageDecoderInternals decoder, Stream stream) + public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel - => decoder.Decode(stream, DefaultLargeImageExceptionFactory); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory); public static Image Decode( this IImageDecoderInternals decoder, + Configuration configuration, Stream stream, Func largeImageExceptionFactory) where TPixel : unmanaged, IPixel { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); try { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 106c1f597..93697db95 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.Decode(configuration, stream); } /// @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.DecodeAsync(stream, cancellationToken); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Identify(stream); + return decoder.Identify(configuration, stream); } /// @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.IdentifyAsync(stream, cancellationToken); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 677a2003e..1ee47a017 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.Decode(configuration, stream); } /// @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.DecodeAsync(stream, cancellationToken); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// @@ -45,14 +45,14 @@ namespace SixLabors.ImageSharp.Formats.Png public IImageInfo Identify(Configuration configuration, Stream stream) { var decoder = new PngDecoderCore(configuration, this); - return decoder.Identify(stream); + return decoder.Identify(configuration, stream); } /// public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { var decoder = new PngDecoderCore(configuration, this); - return decoder.IdentifyAsync(stream, cancellationToken); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index b751a704a..3fa0e3f58 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1028,7 +1028,7 @@ namespace SixLabors.ImageSharp.Formats.Png private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value) { using (var memoryStream = new MemoryStream(compressedData.ToArray())) - using (var bufferedStream = new BufferedReadStream(memoryStream)) + using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStream)) using (var inflateStream = new ZlibInflateStream(bufferedStream)) { if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index bcf52a011..bad769fa9 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - return decoder.Decode(stream); + return decoder.Decode(configuration, stream); } /// @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tga Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - return decoder.DecodeAsync(stream, cancellationToken); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).Identify(stream); + return new TgaDecoderCore(configuration, this).Identify(configuration, stream); } /// @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).IdentifyAsync(stream, cancellationToken); + return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 182f0ac82..58d6640a0 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -891,7 +891,7 @@ namespace SixLabors.ImageSharp } using MemoryStream memoryStream = configuration.MemoryAllocator.AllocateFixedCapacityMemoryStream(stream.Length); - TODO! await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); + await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; return await action(memoryStream, cancellationToken).ConfigureAwait(false); From 103d676bd4cb581034b0c149f30751a0022982d0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 25 Jul 2020 22:46:33 +0200 Subject: [PATCH 07/33] tests for general cancellation --- src/ImageSharp/Image.FromFile.cs | 77 +++++++++++++++++ .../Image/ImageTests.Decode_Cancellation.cs | 82 +++++++++++++++++++ .../Image/ImageTests.DetectFormat.cs | 2 - .../Image/ImageTests.Identify.cs | 2 - .../Image/ImageTests.ImageLoadTestBase.cs | 21 +++-- ..._FileSystemPath_UseDefaultConfiguration.cs | 18 ++++ ...s.Load_FromBytes_PassLocalConfiguration.cs | 2 - .../ImageSharp.Tests/ImageSharp.Tests.csproj | 2 +- tests/ImageSharp.Tests/TestFileSystem.cs | 10 +-- tests/ImageSharp.Tests/TestFormat.cs | 27 ++++-- .../SemaphoreReadMemoryStream.cs | 49 ++++++----- .../Tests/SemaphoreReadMemoryStreamTests.cs | 77 ++++++++--------- 12 files changed, 274 insertions(+), 95 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 7dc06b3ca..7d94e1beb 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -197,6 +197,23 @@ namespace SixLabors.ImageSharp public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder) => LoadAsync(configuration, path, decoder, default); + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Configuration configuration, string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(configuration, path, decoder, default); + /// /// Create a new instance of the class from the given file. /// @@ -219,6 +236,66 @@ namespace SixLabors.ImageSharp return LoadAsync(configuration, stream, decoder, cancellationToken); } + /// + /// Create a new instance of the class from the given file. + /// + /// The Configuration. + /// The file path to the image. + /// The decoder. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Configuration configuration, string path, IImageDecoder decoder, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(path, nameof(path)); + + using Stream stream = configuration.FileSystem.OpenRead(path); + return LoadAsync(configuration, stream, decoder, cancellationToken); + } + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration for the decoder. + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Configuration configuration, string path) + where TPixel : unmanaged, IPixel + => LoadAsync(configuration, path, default(CancellationToken)); + + /// + /// Create a new instance of the class from the given file. + /// + /// The configuration for the decoder. + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync(Configuration configuration, string path, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using Stream stream = configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } + /// /// Create a new instance of the class from the given file. /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs new file mode 100644 index 000000000..4e278ad35 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + public class Decode_Cancellation : ImageLoadTestBase + { + private bool isTestStreamSeekable; + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + + public Decode_Cancellation() + { + this.TopLevelConfiguration.StreamProcessingBufferSize = 128; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task LoadAsync_Specific_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task LoadAsync_Agnostic_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task LoadAsync_Agnostic_Path_WhenCancelledDuringRead() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Fact] + public async Task LoadAsync_Specific_Path_WhenCancelledDuringRead() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + private async Task DoCancel() + { + // wait until we reach the middle of the steam + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + + // set the cancellation + this.cts.Cancel(); + + // continue processing the stream + this.continueSemaphore.Release(); + } + + protected override Stream CreateStream() => this.TestFormat.CreateAsyncSamaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index 6327b3eac..5f3b16806 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -24,8 +24,6 @@ namespace SixLabors.ImageSharp.Tests private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); - private byte[] ByteArray => this.DataStream.ToArray(); - private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private static readonly IImageFormat ExpectedGlobalFormat = diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 69b1d21a6..6784f96e8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -21,8 +21,6 @@ namespace SixLabors.ImageSharp.Tests private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - private byte[] ByteArray => this.DataStream.ToArray(); - private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index ea1e6e0d7..44d7daa74 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests { public abstract class ImageLoadTestBase : IDisposable { + private Lazy dataStreamLazy; + protected Image localStreamReturnImageRgba32; protected Image localStreamReturnImageAgnostic; @@ -46,10 +48,12 @@ namespace SixLabors.ImageSharp.Tests public byte[] Marker { get; } - public MemoryStream DataStream { get; } + public Stream DataStream => this.dataStreamLazy.Value; public byte[] DecodedData { get; private set; } + protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); + protected ImageLoadTestBase() { this.localStreamReturnImageRgba32 = new Image(1, 1); @@ -61,9 +65,8 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); - this.localDecoder = detector.As(); - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + this.localDecoder = detector.As(); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -86,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests }) .Returns(this.localStreamReturnImageAgnostic); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + this.LocalConfiguration = new Configuration(); this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); @@ -93,10 +98,12 @@ namespace SixLabors.ImageSharp.Tests this.TopLevelConfiguration = new Configuration(this.TestFormat); this.Marker = Guid.NewGuid().ToByteArray(); - this.DataStream = this.TestFormat.CreateStream(this.Marker); - this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); - this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream); + this.dataStreamLazy = new Lazy(this.CreateStream); + Stream StreamFactory() => this.DataStream; + + this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory); + this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory); this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; } @@ -107,6 +114,8 @@ namespace SixLabors.ImageSharp.Tests this.localStreamReturnImageRgba32?.Dispose(); this.localStreamReturnImageAgnostic?.Dispose(); } + + protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 77e5679f6..1689c1dea 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -49,6 +49,15 @@ namespace SixLabors.ImageSharp.Tests } } + [Fact] + public async Task Path_Specific_Async() + { + using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) + { + VerifyDecodedImage(img); + } + } + [Fact] public async Task Path_Agnostic_Configuration_Async() { @@ -85,6 +94,15 @@ namespace SixLabors.ImageSharp.Tests } } + [Fact] + public async Task Path_Decoder_Specific_Async() + { + using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) + { + VerifyDecodedImage(img); + } + } + [Fact] public void Path_OutFormat_Specific() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs index d275a11d8..320f3696d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Tests { public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase { - private byte[] ByteArray => this.DataStream.ToArray(); - private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); [Theory] diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index f1c3aa28e..97fd1743d 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs index 960f13637..da4362956 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests /// public class TestFileSystem : ImageSharp.IO.IFileSystem { - private readonly Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> fileSystem = new Dictionary>(StringComparer.OrdinalIgnoreCase); - public void AddFile(string path, Stream data) + public void AddFile(string path, Func data) { lock (this.fileSystem) { @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests { if (this.fileSystem.ContainsKey(path)) { - Stream stream = this.fileSystem[path]; + Stream stream = this.fileSystem[path](); stream.Position = 0; return stream; } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests { if (this.fileSystem.ContainsKey(path)) { - Stream stream = this.fileSystem[path]; + Stream stream = this.fileSystem[path](); stream.Position = 0; return stream; } @@ -54,4 +54,4 @@ namespace SixLabors.ImageSharp.Tests return File.OpenRead(path); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index aa986aa8a..18f0876d0 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -33,9 +34,9 @@ namespace SixLabors.ImageSharp.Tests public List DecodeCalls { get; } = new List(); - public IImageEncoder Encoder { get; } + public TestEncoder Encoder { get; } - public IImageDecoder Decoder { get; } + public TestDecoder Decoder { get; } private byte[] header = Guid.NewGuid().ToByteArray(); @@ -53,6 +54,14 @@ namespace SixLabors.ImageSharp.Tests return ms; } + public Stream CreateAsyncSamaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) + { + var buffer = new byte[size]; + this.header.CopyTo(buffer, 0); + var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); + return seeakable ? (Stream)semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); + } + public void VerifySpecificDecodeCall(byte[] marker, Configuration config) where TPixel : unmanaged, IPixel { @@ -205,9 +214,17 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration config, Stream stream) where TPixel : unmanaged, IPixel + => this.DecodeImpl(config, stream, default).GetAwaiter().GetResult(); + + public Task> DecodeAsync(Configuration config, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.DecodeImpl(config, stream, cancellationToken); + + private async Task> DecodeImpl(Configuration config, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); - stream.CopyTo(ms); + await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken); var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { @@ -220,10 +237,6 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public Task> DecodeAsync(Configuration config, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => Task.FromResult(this.Decode(config, stream)); - public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); diff --git a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs index 296611bb5..f03d2c493 100644 --- a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs +++ b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs @@ -10,62 +10,59 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities { internal class SemaphoreReadMemoryStream : MemoryStream { - private SemaphoreSlim waitSemaphore; - private readonly SemaphoreSlim signalFinishedSemaphore; - private readonly long waitAfterPosition; + private readonly SemaphoreSlim continueSemaphore; + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore; + private int pauseDone; + private readonly long waitPosition; - public SemaphoreReadMemoryStream(byte[] buffer, SemaphoreSlim waitSemaphore, SemaphoreSlim signalFinishedSemaphore, long waitAfterPosition) + public SemaphoreReadMemoryStream( + byte[] buffer, + long waitPosition, + SemaphoreSlim notifyWaitPositionReachedSemaphore, + SemaphoreSlim continueSemaphore) : base(buffer) { - this.waitSemaphore = waitSemaphore; - this.signalFinishedSemaphore = signalFinishedSemaphore; - this.waitAfterPosition = waitAfterPosition; + this.continueSemaphore = continueSemaphore; + this.notifyWaitPositionReachedSemaphore = notifyWaitPositionReachedSemaphore; + this.waitPosition = waitPosition; } public override int Read(byte[] buffer, int offset, int count) { int read = base.Read(buffer, offset, count); - if (this.Position + read > this.waitAfterPosition) + if (this.Position > this.waitPosition && this.TryPause()) { - this.waitSemaphore.Wait(); + this.notifyWaitPositionReachedSemaphore.Release(); + this.continueSemaphore.Wait(); } - this.SignalIfFinished(); - return read; } + private bool TryPause() => Interlocked.CompareExchange(ref this.pauseDone, 1, 0) == 0; + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { int read = await base.ReadAsync(buffer, offset, count, cancellationToken); - if (this.Position + read > this.waitAfterPosition) + if (this.Position > this.waitPosition && this.TryPause()) { - await this.waitSemaphore.WaitAsync(); + this.notifyWaitPositionReachedSemaphore.Release(); + await this.continueSemaphore.WaitAsync(); } - this.SignalIfFinished(); - return read; } public override int ReadByte() { - if (this.Position + 1 > this.waitAfterPosition) + if (this.Position + 1 > this.waitPosition && this.TryPause()) { - this.waitSemaphore.Wait(); + this.notifyWaitPositionReachedSemaphore.Release(); + this.continueSemaphore.Wait(); } int result = base.ReadByte(); - this.SignalIfFinished(); return result; } - - private void SignalIfFinished() - { - if (this.Position == this.Length) - { - this.signalFinishedSemaphore.Release(); - } - } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs index ac90d5fb2..14f5a9c0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -11,83 +11,72 @@ namespace SixLabors.ImageSharp.Tests { public class SemaphoreReadMemoryStreamTests { - private readonly SemaphoreSlim WaitSemaphore = new SemaphoreSlim(0); - private readonly SemaphoreSlim FinishedSemaphore = new SemaphoreSlim(0); - private readonly byte[] Buffer = new byte[128]; + private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); + private readonly byte[] buffer = new byte[128]; [Fact] public void Read_BeforeWaitLimit_ShouldFinish() { - using Stream stream = this.GetStream(); - int read = stream.Read(this.Buffer); - Assert.Equal(this.Buffer.Length, read); + using Stream stream = this.CreateTestStream(); + int read = stream.Read(this.buffer); + Assert.Equal(this.buffer.Length, read); } [Fact] public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() { - using Stream stream = this.GetStream(); - int read = await stream.ReadAsync(this.Buffer); - Assert.Equal(this.Buffer.Length, read); + using Stream stream = this.CreateTestStream(); + int read = await stream.ReadAsync(this.buffer); + Assert.Equal(this.buffer.Length, read); } [Fact] public async Task Read_AfterWaitLimit_ShouldPause() { - using Stream stream = this.GetStream(); - stream.Read(this.Buffer); + using Stream stream = this.CreateTestStream(); + stream.Read(this.buffer); - Task readTask = Task.Factory.StartNew(() => stream.Read(new byte[512]), TaskCreationOptions.LongRunning); + Task readTask = Task.Factory.StartNew( + () => + { + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + }, TaskCreationOptions.LongRunning); + Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); await Task.Delay(5); Assert.False(readTask.IsCompleted); - this.WaitSemaphore.Release(); + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.continueSemaphore.Release(); await readTask; } [Fact] public async Task ReadAsync_AfterWaitLimit_ShouldPause() { - using Stream stream = this.GetStream(); - await stream.ReadAsync(this.Buffer); + using Stream stream = this.CreateTestStream(); + await stream.ReadAsync(this.buffer); Task readTask = Task.Factory.StartNew(() => stream.ReadAsync(new byte[512]).AsTask(), TaskCreationOptions.LongRunning); await Task.Delay(5); Assert.False(readTask.IsCompleted); - this.WaitSemaphore.Release(); + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.continueSemaphore.Release(); await readTask; } - [Fact] - public async Task Read_WhenFinished_ShouldNotify() - { - using Stream stream = this.GetStream(512, int.MaxValue); - stream.Read(this.Buffer); - stream.Read(this.Buffer); - stream.Read(this.Buffer); - Assert.Equal(0, this.FinishedSemaphore.CurrentCount); - stream.Read(this.Buffer); - Assert.Equal(1, this.FinishedSemaphore.CurrentCount); - } - - [Fact] - public async Task ReadAsync_WhenFinished_ShouldNotify() - { - using Stream stream = this.GetStream(512, int.MaxValue); - await stream.ReadAsync(this.Buffer); - await stream.ReadAsync(this.Buffer); - await stream.ReadAsync(this.Buffer); - Assert.Equal(0, this.FinishedSemaphore.CurrentCount); - - Task lastRead = stream.ReadAsync(this.Buffer).AsTask(); - Task finishedTask = this.FinishedSemaphore.WaitAsync(); - await Task.WhenAll(lastRead, finishedTask); - } - - private Stream GetStream(int size = 1024, int waitAfterPosition = 256) + private Stream CreateTestStream(int size = 1024, int waitAfterPosition = 256) { byte[] buffer = new byte[size]; - return new SemaphoreReadMemoryStream(buffer, this.WaitSemaphore, this.FinishedSemaphore, waitAfterPosition); + return new SemaphoreReadMemoryStream(buffer, waitAfterPosition, this.notifyWaitPositionReachedSemaphore, this.continueSemaphore); } } } From 0aa56f1cd3c6f8df7e8c9ae5517d0fe89a9f0e59 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 25 Jul 2020 23:51:30 +0200 Subject: [PATCH 08/33] tons of new overloads --- src/ImageSharp/Image.FromFile.cs | 163 ++++++++++++++++++ src/ImageSharp/Image.FromStream.cs | 41 ++++- .../Image/ImageTests.Decode_Cancellation.cs | 56 +++++- .../Image/ImageTests.Identify.cs | 43 ++++- ..._FileSystemPath_UseDefaultConfiguration.cs | 84 ++++----- tests/ImageSharp.Tests/TestFormat.cs | 8 +- 6 files changed, 337 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 7d94e1beb..1058dd19c 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -80,6 +80,125 @@ namespace SixLabors.ImageSharp } } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath) + => IdentifyAsync(Configuration.Default, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(Configuration configuration, string filePath) + => IdentifyAsync(configuration, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken) + => IdentifyAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task IdentifyAsync(Configuration configuration, string filePath, CancellationToken cancellationToken) + { + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken); + return res.ImageInfo; + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(string filePath) + => IdentifyWithFormatAsync(Configuration.Default, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + string filePath, + CancellationToken cancellationToken) + => IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + string filePath) + => IdentifyWithFormatAsync(configuration, filePath, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + string filePath, + CancellationToken cancellationToken) + { + Guard.NotNull(configuration, nameof(configuration)); + using Stream stream = configuration.FileSystem.OpenRead(filePath); + return await IdentifyWithFormatAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + } + /// /// Create a new instance of the class from the given file. /// @@ -182,6 +301,20 @@ namespace SixLabors.ImageSharp } } + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, IImageDecoder decoder) + => LoadAsync(Configuration.Default, path, decoder, default); + /// /// Create a new instance of the class from the given file. /// @@ -197,6 +330,22 @@ namespace SixLabors.ImageSharp public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder) => LoadAsync(configuration, path, decoder, default); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, decoder, default); + /// /// Create a new instance of the class from the given file. /// @@ -260,6 +409,20 @@ namespace SixLabors.ImageSharp return LoadAsync(configuration, stream, decoder, cancellationToken); } + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path) + where TPixel : unmanaged, IPixel + => LoadAsync(Configuration.Default, path, default(CancellationToken)); + /// /// Create a new instance of the class from the given file. /// diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 58d6640a0..ac6eec282 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -135,9 +135,26 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation or null if /// a suitable detector is not found. /// - public static async Task IdentifyAsync(Configuration configuration, Stream stream) + public static Task IdentifyAsync(Configuration configuration, Stream stream) + => IdentifyAsync(configuration, stream, default); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream).ConfigureAwait(false); + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false); return res.ImageInfo; } @@ -197,6 +214,24 @@ namespace SixLabors.ImageSharp (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), default); + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Stream stream, + CancellationToken cancellationToken) + => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken); + /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -208,7 +243,7 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// Image contains invalid content. /// - /// The representing the asyncronous operation with the parameter type + /// The representing the asynchronous operation with the parameter type /// property set to null if suitable info detector is not found. /// public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs index 4e278ad35..317a5129c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Specific_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Agnostic_Stream_WhenCancelledDuringRead(bool isInputStreamSeekable) + public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public async Task LoadAsync_Agnostic_Path_WhenCancelledDuringRead() + public async Task LoadAsync_Agnostic_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public async Task LoadAsync_Specific_Path_WhenCancelledDuringRead() + public async Task LoadAsync_Specific_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); @@ -64,6 +64,54 @@ namespace SixLabors.ImageSharp.Tests await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyAsync_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + } + + [Fact] + public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + { + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); + } + private async Task DoCancel() { // wait until we reach the middle of the steam diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 6784f96e8..e5b35ffd2 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests { private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + private static readonly Size ExpectedImageSize = new Size(108, 202); + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; @@ -33,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests { IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); - Assert.NotNull(info); + Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedGlobalFormat, type); } @@ -131,13 +133,46 @@ namespace SixLabors.ImageSharp.Tests using (var stream = new MemoryStream(this.ActualImageBytes)) { var asyncStream = new AsyncStreamWrapper(stream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); - Assert.NotNull(info.ImageInfo); - Assert.Equal(ExpectedGlobalFormat, info.Format); + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); } } + [Fact] + public async Task FromPathAsync_CustomConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath); + Assert.Equal(this.LocalImageInfo, info); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath); + Assert.NotNull(info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } + + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } + + [Fact] + public async Task FromPathAsync_GlobalConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, info.Size()); + } + + [Fact] public async Task FromStreamAsync_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 1689c1dea..a5034e43b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -25,102 +25,80 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Path_Specific() { - using (var img = Image.Load(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); } [Fact] public void Path_Agnostic() { - using (var img = Image.Load(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Agnostic_Async() { - using (var img = await Image.LoadAsync(this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Specific_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public async Task Path_Agnostic_Configuration_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path)) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); } [Fact] public void Path_Decoder_Specific() { - using (var img = Image.Load(this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public void Path_Decoder_Agnostic() { - using (var img = Image.Load(this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public async Task Path_Decoder_Agnostic_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public async Task Path_Decoder_Specific_Async() { - using (var img = await Image.LoadAsync(Configuration.Default, this.Path, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + VerifyDecodedImage(img); } [Fact] public void Path_OutFormat_Specific() { - using (var img = Image.Load(this.Path, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] public void Path_OutFormat_Agnostic() { - using (var img = Image.Load(this.Path, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] @@ -142,6 +120,20 @@ namespace SixLabors.ImageSharp.Tests Image.Load((string)null); }); } + + [Fact] + public Task Async_WhenFileNotFound_Throws() + { + return Assert.ThrowsAsync( + () => Image.LoadAsync(Guid.NewGuid().ToString())); + } + + [Fact] + public Task Async_WhenPathIsNull_Throws() + { + return Assert.ThrowsAsync( + () => Image.LoadAsync((string)null)); + } } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 18f0876d0..5a380f783 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests } } - public class TestDecoder : IImageDecoder + public class TestDecoder : IImageDecoder, IImageInfoDetector { private TestFormat testFormat; @@ -243,6 +243,12 @@ namespace SixLabors.ImageSharp.Tests public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); + + public IImageInfo Identify(Configuration configuration, Stream stream) => + this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult(); + + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeImpl(configuration, stream, cancellationToken); } public class TestEncoder : ImageSharp.Formats.IImageEncoder From 9622534276476bbcc033c68be71581138c0bafeb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 00:38:37 +0200 Subject: [PATCH 09/33] implement cancellation for Jpeg --- .../Components/Decoder/HuffmanScanDecoder.cs | 17 ++++++- .../Decoder/JpegImagePostProcessor.cs | 5 +- .../Formats/Jpeg/JpegDecoderCore.cs | 21 ++++---- src/ImageSharp/Image.FromFile.cs | 14 ++++++ .../Formats/Jpg/JpegDecoderTests.cs | 48 ++++++++++++++++++- .../Jpg/JpegImagePostProcessorTests.cs | 2 +- .../ImageProviders/FileProvider.cs | 12 ++++- .../ImageProviders/TestImageProvider.cs | 6 +++ 8 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index d6c16f826..6424ee23a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -50,6 +51,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private HuffmanScanBuffer scanBuffer; + private CancellationToken cancellationToken; + /// /// Initializes a new instance of the class. /// @@ -63,6 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The spectral selection end. /// The successive approximation bit high end. /// The successive approximation bit low end. + /// The token to monitor cancellation. public HuffmanScanDecoder( BufferedReadStream stream, JpegFrame frame, @@ -73,7 +77,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int spectralStart, int spectralEnd, int successiveHigh, - int successiveLow) + int successiveLow, + CancellationToken cancellationToken) { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; @@ -89,6 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.spectralEnd = spectralEnd; this.successiveHigh = successiveHigh; this.successiveLow = successiveLow; + this.cancellationToken = cancellationToken; } /// @@ -96,6 +102,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public void ParseEntropyCodedData() { + this.cancellationToken.ThrowIfCancellationRequested(); + if (!this.frame.Progressive) { this.ParseBaselineData(); @@ -145,6 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < mcusPerColumn; j++) { + this.cancellationToken.ThrowIfCancellationRequested(); + for (int i = 0; i < mcusPerLine; i++) { // Scan an interleaved mcu... process components in order @@ -210,6 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { + this.cancellationToken.ThrowIfCancellationRequested(); Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); @@ -376,6 +387,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { + this.cancellationToken.ThrowIfCancellationRequested(); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); @@ -402,6 +415,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { + this.cancellationToken.ThrowIfCancellationRequested(); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 716bb9eb0..5b0331c85 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Numerics; +using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -111,7 +112,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// The pixel type /// The destination image - public void PostProcess(ImageFrame destination) + /// The token to request cancellation. + public void PostProcess(ImageFrame destination, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { this.PixelRowCounter = 0; @@ -123,6 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height) { + cancellationToken.ThrowIfCancellationRequested(); this.DoPostProcessorStep(destination); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 6874d09e9..70f3a9202 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -212,18 +212,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.ParseStream(stream); + this.ParseStream(stream, cancellationToken: cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return this.PostProcessIntoImage(); + return this.PostProcessIntoImage(cancellationToken); } /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ParseStream(stream, true); + this.ParseStream(stream, true, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); @@ -237,7 +237,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The input stream /// Whether to decode metadata only. - public void ParseStream(BufferedReadStream stream, bool metadataOnly = false) + /// The token to monitor cancellation. + public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default) { this.Metadata = new ImageMetadata(); @@ -283,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg case JpegConstants.Markers.SOS: if (!metadataOnly) { - this.ProcessStartOfScanMarker(stream); + this.ProcessStartOfScanMarker(stream, cancellationToken); break; } else @@ -990,8 +991,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Processes the SOS (Start of scan marker). /// - /// The input stream. - private void ProcessStartOfScanMarker(BufferedReadStream stream) + private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken) { if (this.Frame is null) { @@ -1042,7 +1042,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg spectralStart, spectralEnd, successiveApproximation >> 4, - successiveApproximation & 15); + successiveApproximation & 15, + cancellationToken); sd.ParseEntropyCodedData(); } @@ -1075,7 +1076,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The . - private Image PostProcessIntoImage() + private Image PostProcessIntoImage(CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { if (this.ImageWidth == 0 || this.ImageHeight == 0) @@ -1091,7 +1092,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this)) { - postProcessor.PostProcess(image.Frames.RootFrame); + postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken); } return image; diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 1058dd19c..d3464d20d 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -301,6 +301,20 @@ namespace SixLabors.ImageSharp } } + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, CancellationToken cancellationToken) + => LoadAsync(Configuration.Default, path, cancellationToken); + /// /// Create a new instance of the class from the given file. /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 912f606b2..2bc64789e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; @@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); @@ -112,6 +114,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.IsType(ex.InnerException); } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public async Task DecodeAsnc_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); + } + + [Theory] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 3)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 3)] + public async Task Decode_IsCancellable(string fileName, int waitMilliseconds) + { + string hugeFile = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + fileName); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(waitMilliseconds); + await Assert.ThrowsAsync(() => Image.LoadAsync(hugeFile, cts.Token)); + } + + [Theory] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 3)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 3)] + public async Task Identify_IsCancellable(string fileName, int waitMilliseconds) + { + string hugeFile = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + fileName); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(waitMilliseconds); + await Assert.ThrowsAsync(() => Image.IdentifyAsync(hugeFile, cts.Token)); + } + // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index 12e1ec22b..0dd2abcc1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder)) using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) { - pp.PostProcess(image.Frames.RootFrame); + pp.PostProcess(image.Frames.RootFrame, default); image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 78567f926..440baaa63 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -4,8 +4,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Reflection; - +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -164,6 +165,15 @@ namespace SixLabors.ImageSharp.Tests return cachedImage.Clone(this.Configuration); } + public override Task> GetImageAsync(IImageDecoder decoder) + { + Guard.NotNull(decoder, nameof(decoder)); + + // Used in small subset of decoder tests, no caching. + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + return Image.LoadAsync(this.Configuration, path, decoder); + } + public override void Deserialize(IXunitSerializationInfo info) { this.FilePath = info.GetValue("path"); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index da641a296..700c40b72 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -3,6 +3,7 @@ using System; using System.Reflection; +using System.Threading.Tasks; using Castle.Core.Internal; using SixLabors.ImageSharp.Formats; @@ -97,6 +98,11 @@ namespace SixLabors.ImageSharp.Tests throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); } + public virtual Task> GetImageAsync(IImageDecoder decoder) + { + throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); + } + /// /// Returns an instance to the test case with the necessary traits. /// From 3c55f5d550a7c2e245d8d3ac8b6436b8d6e29cd7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 01:12:36 +0200 Subject: [PATCH 10/33] better cancellation in jpeg --- .../Formats/Jpeg/JpegDecoderCore.cs | 2 ++ src/ImageSharp/Image.FromFile.cs | 3 +- .../Formats/Jpg/JpegDecoderTests.cs | 30 ++++++++++++------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 70f3a9202..083cae240 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -268,6 +268,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { + cancellationToken.ThrowIfCancellationRequested(); + if (!fileMarker.Invalid) { // Get the marker length diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index d3464d20d..eeb15e2ae 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -131,7 +131,8 @@ namespace SixLabors.ImageSharp /// public static async Task IdentifyAsync(Configuration configuration, string filePath, CancellationToken cancellationToken) { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken); + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken) + .ConfigureAwait(false); return res.ImageInfo; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 2bc64789e..086798cef 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -127,34 +127,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 0)] [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 3)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 10)] + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 30)] [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 3)] - public async Task Decode_IsCancellable(string fileName, int waitMilliseconds) + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 10)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 30)] + public async Task Decode_IsCancellable(string fileName, int cancellationDelayMs) { + // Decoding these huge files took 300ms on i7-8650U in 2020. 30ms should be safe for cancellation delay. string hugeFile = Path.Combine( TestEnvironment.InputImagesDirectoryFullPath, fileName); var cts = new CancellationTokenSource(); - cts.CancelAfter(waitMilliseconds); + if (cancellationDelayMs == 0) + { + cts.Cancel(); + } + else + { + cts.CancelAfter(cancellationDelayMs); + } + await Assert.ThrowsAsync(() => Image.LoadAsync(hugeFile, cts.Token)); } [Theory] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 3)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 3)] - public async Task Identify_IsCancellable(string fileName, int waitMilliseconds) + [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform)] + [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518)] + public async Task Identify_IsCancellable(string fileName) { string hugeFile = Path.Combine( TestEnvironment.InputImagesDirectoryFullPath, fileName); var cts = new CancellationTokenSource(); - cts.CancelAfter(waitMilliseconds); + cts.CancelAfter(TimeSpan.FromTicks(1)); await Assert.ThrowsAsync(() => Image.IdentifyAsync(hugeFile, cts.Token)); } From 0250a046a02a20ff5d308ec8aa9defba6943290a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 01:43:52 +0200 Subject: [PATCH 11/33] cancellation support for encoders --- .../Advanced/AdvancedImageExtensions.cs | 6 ++- src/ImageSharp/Advanced/IImageVisitor.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 5 ++- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 30 ++----------- src/ImageSharp/Formats/Gif/GifEncoder.cs | 5 ++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 23 ++-------- src/ImageSharp/Formats/IImageEncoder.cs | 4 +- .../Formats/IImageEncoderInternals.cs | 25 +++++++++++ .../Formats/ImageEncoderUtilities.cs | 43 +++++++++++++++++++ src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 20 ++------- .../Formats/Jpeg/JpegEncoderCore.cs | 8 ++-- src/ImageSharp/Formats/Png/PngEncoder.cs | 6 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 30 ++----------- src/ImageSharp/Formats/Tga/TgaEncoder.cs | 5 ++- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 30 ++----------- src/ImageSharp/Image.cs | 21 +++++++-- src/ImageSharp/Image{TPixel}.cs | 5 ++- .../Image/ImageTests.Identify.cs | 1 - tests/ImageSharp.Tests/TestFormat.cs | 2 +- .../SystemDrawingReferenceEncoder.cs | 3 +- 20 files changed, 139 insertions(+), 137 deletions(-) create mode 100644 src/ImageSharp/Formats/IImageEncoderInternals.cs create mode 100644 src/ImageSharp/Formats/ImageEncoderUtilities.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 5791c6e92..19d6334e3 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -73,9 +74,10 @@ namespace SixLabors.ImageSharp.Advanced /// /// The source image. /// The image visitor. + /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. - public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor) - => source.AcceptAsync(visitor); + public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken) + => source.AcceptAsync(visitor, cancellationToken); /// /// Gets the configuration for the image. diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index fbfdafeb1..ccff18026 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; @@ -31,9 +32,10 @@ namespace SixLabors.ImageSharp.Advanced /// Provides a pixel-specific implementation for a given operation. /// /// The image. + /// The token to monitor for cancellation requests. /// The pixel type. /// A representing the asynchronous operation. - Task VisitAsync(Image image) + Task VisitAsync(Image image, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 08c9bde00..2f5c4b7cf 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -42,11 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream); + return encoder.EncodeAsync(image, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b3f64eea6..eb29c4405 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; @@ -19,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image encoder for writing an image to a stream as a Windows bitmap. /// - internal sealed class BmpEncoderCore + internal sealed class BmpEncoderCore : IImageEncoderInternals { /// /// The amount to pad each row by. @@ -97,32 +98,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The pixel format. /// The to encode from. /// The to encode the image data to. - public async Task EncodeAsync(Image image, Stream stream) + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - { - if (stream.CanSeek) - { - this.Encode(image, stream); - } - else - { - using (var ms = new MemoryStream()) - { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); - } - } - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 539ab0fb3..116ee3dae 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -41,11 +42,11 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var encoder = new GifEncoderCore(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream); + return encoder.EncodeAsync(image, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 556ace203..070864e60 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Implements the GIF encoding protocol. /// - internal sealed class GifEncoderCore + internal sealed class GifEncoderCore : IImageEncoderInternals { /// /// Used for allocating memory during processing operations. @@ -75,25 +76,9 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode from. /// The to encode the image data to. - public async Task EncodeAsync(Image image, Stream stream) + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - { - using (var ms = new MemoryStream()) - { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); - } - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 646e0ecc0..e5a1b1c83 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; @@ -27,8 +28,9 @@ namespace SixLabors.ImageSharp.Formats /// 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. - Task EncodeAsync(Image image, Stream stream) + Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoderInternals.cs b/src/ImageSharp/Formats/IImageEncoderInternals.cs new file mode 100644 index 000000000..d44ac45f2 --- /dev/null +++ b/src/ImageSharp/Formats/IImageEncoderInternals.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// + internal interface IImageEncoderInternals + { + /// + /// Encodes the image. + /// + /// The image. + /// The stream. + /// The token to monitor for cancellation requests. + /// The pixel type. + void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs new file mode 100644 index 000000000..29f0a14c3 --- /dev/null +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +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) + { + encoder.Encode(image, stream, cancellationToken); + } + else + { + using var ms = new MemoryStream(); + encoder.Encode(image, ms, cancellationToken); + ms.Position = 0; + await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) + .ConfigureAwait(false); + } + } + + 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/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 08d793401..b549bd8a3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; @@ -43,26 +44,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 async Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); - - if (stream.CanSeek) - { - encoder.Encode(image, stream); - } - else - { - // this hack has to be be here because JpegEncoderCore is unsafe - using (var ms = new MemoryStream()) - { - encoder.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); - } - } + return encoder.EncodeAsync(image, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index f755028db..acc639eb7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal sealed unsafe class JpegEncoderCore + internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals { /// /// The number of quantization tables. @@ -194,8 +195,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The image to write from. /// The stream to write to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 61ea7c468..7e563596e 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -71,13 +72,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 async Task EncodeAsync(Image image, Stream stream) + public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { - await encoder.EncodeAsync(image, stream).ConfigureAwait(false); + await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index a39fdd91f..5cf11099c 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -7,6 +7,7 @@ using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png.Chunks; @@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Performs the png encoding operation. /// - internal sealed class PngEncoderCore : IDisposable + internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { /// /// The maximum block size, defaults at 64k for uncompressed blocks. @@ -127,31 +128,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The to encode from. /// The to encode the image data to. - public async Task EncodeAsync(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - if (stream.CanSeek) - { - this.Encode(image, stream); - } - else - { - using (var ms = new MemoryStream()) - { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); - } - } - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index c8f8fb1a5..529d951cd 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -32,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream); + return encoder.EncodeAsync(image, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index d9e7c0e6b..d3a628531 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Image encoder for writing an image to a stream as a truevision targa image. /// - internal sealed class TgaEncoderCore + internal sealed class TgaEncoderCore : IImageEncoderInternals { /// /// Used for allocating memory during processing operations. @@ -61,31 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The pixel format. /// The to encode from. /// The to encode the image data to. - public async Task EncodeAsync(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - if (stream.CanSeek) - { - this.Encode(image, stream); - } - else - { - using (var ms = new MemoryStream()) - { - this.Encode(image, ms); - ms.Position = 0; - await ms.CopyToAsync(stream).ConfigureAwait(false); - } - } - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 605f5e0da..3560d9960 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -106,12 +107,23 @@ namespace SixLabors.ImageSharp /// Thrown if the stream or encoder is null. /// A representing the asynchronous operation. public Task SaveAsync(Stream stream, IImageEncoder encoder) + => this.SaveAsync(stream, encoder, default); + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// 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, IImageEncoder encoder, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); this.EnsureNotDisposed(); - return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream)); + return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken); } /// @@ -162,7 +174,8 @@ namespace SixLabors.ImageSharp /// with the pixel type of the image. /// /// The visitor. - internal abstract Task AcceptAsync(IImageVisitorAsync visitor); + /// The token to monitor for cancellation requests. + internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken); private class EncodeVisitor : IImageVisitor, IImageVisitorAsync { @@ -179,8 +192,8 @@ namespace SixLabors.ImageSharp public void Visit(Image image) where TPixel : unmanaged, IPixel => this.encoder.Encode(image, this.stream); - public Task VisitAsync(Image image) - where TPixel : unmanaged, IPixel => this.encoder.EncodeAsync(image, this.stream); + public Task VisitAsync(Image image, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => this.encoder.EncodeAsync(image, this.stream, cancellationToken); } } } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 9d3abc1e8..255193c8e 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -290,11 +291,11 @@ namespace SixLabors.ImageSharp } /// - internal override Task AcceptAsync(IImageVisitorAsync visitor) + internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken) { this.EnsureNotDisposed(); - return visitor.VisitAsync(this); + return visitor.VisitAsync(this, cancellationToken); } /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index e5b35ffd2..72de3fcc4 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -172,7 +172,6 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(ExpectedImageSize, info.Size()); } - [Fact] public async Task FromStreamAsync_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 5a380f783..7273a65f7 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -270,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests // TODO record this happened so we can verify it. } - public Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // TODO record this happened so we can verify it. diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 2aaf7bf80..4c28079c1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -3,6 +3,7 @@ using System.Drawing.Imaging; using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -31,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task EncodeAsync(Image image, Stream stream) + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) From 8226cc4715f7f2af723a80347357fcdea12e248f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 02:24:20 +0200 Subject: [PATCH 12/33] cancellation for Image.SaveAs*** --- src/ImageSharp/Formats/Bmp/ImageExtensions.cs | 100 --- src/ImageSharp/Formats/Gif/ImageExtensions.cs | 100 --- src/ImageSharp/Formats/ImageExtesions.Save.cs | 694 ++++++++++++++++++ src/ImageSharp/Formats/ImageExtesions.Save.tt | 180 +++++ .../Formats/Jpeg/ImageExtensions.cs | 100 --- src/ImageSharp/Formats/Png/ImageExtensions.cs | 100 --- src/ImageSharp/Formats/Tga/ImageExtensions.cs | 100 --- src/ImageSharp/ImageExtensions.cs | 37 +- src/ImageSharp/ImageSharp.csproj | 9 + 9 files changed, 915 insertions(+), 505 deletions(-) delete mode 100644 src/ImageSharp/Formats/Bmp/ImageExtensions.cs delete mode 100644 src/ImageSharp/Formats/Gif/ImageExtensions.cs create mode 100644 src/ImageSharp/Formats/ImageExtesions.Save.cs create mode 100644 src/ImageSharp/Formats/ImageExtesions.Save.tt delete mode 100644 src/ImageSharp/Formats/Jpeg/ImageExtensions.cs delete mode 100644 src/ImageSharp/Formats/Png/ImageExtensions.cs delete mode 100644 src/ImageSharp/Formats/Tga/ImageExtensions.cs diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs deleted file mode 100644 index 8d97c8b46..000000000 --- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Bmp; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream) => SaveAsBmp(source, stream, null); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream) => SaveAsBmpAsync(source, stream, null); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - } -} diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs deleted file mode 100644 index d262b056c..000000000 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Gif; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream) => SaveAsGif(source, stream, null); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream) => SaveAsGifAsync(source, stream, null); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - } -} diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.cs b/src/ImageSharp/Formats/ImageExtesions.Save.cs new file mode 100644 index 000000000..1cf5bf2a7 --- /dev/null +++ b/src/ImageSharp/Formats/ImageExtesions.Save.cs @@ -0,0 +1,694 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; + +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsBmpAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder) + => SaveAsBmpAsync(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsBmp(this Image source, Stream stream) + => SaveAsBmp(source, stream, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream) => SaveAsBmpAsync(source, stream, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAsBmpAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder) + => SaveAsBmpAsync(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsGifAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder) + => SaveAsGifAsync(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsGif(this Image source, Stream stream) + => SaveAsGif(source, stream, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream) => SaveAsGifAsync(source, stream, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAsGifAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder) + => SaveAsGifAsync(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsJpegAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder) + => SaveAsJpegAsync(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this Image source, Stream stream) + => SaveAsJpeg(source, stream, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream) => SaveAsJpegAsync(source, stream, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAsJpegAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder) + => SaveAsJpegAsync(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsPngAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder) + => SaveAsPngAsync(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPng(this Image source, Stream stream) + => SaveAsPng(source, stream, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream) => SaveAsPngAsync(source, stream, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAsPngAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder) + => SaveAsPngAsync(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsTgaAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder) + => SaveAsTgaAsync(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsTga(this Image source, Stream stream) + => SaveAsTga(source, stream, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream) => SaveAsTgaAsync(source, stream, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAsTgaAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder) + => SaveAsTgaAsync(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), + cancellationToken); + + } +} diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.tt b/src/ImageSharp/Formats/ImageExtesions.Save.tt new file mode 100644 index 000000000..37aaf5bb7 --- /dev/null +++ b/src/ImageSharp/Formats/ImageExtesions.Save.tt @@ -0,0 +1,180 @@ +<#@ template language="C#" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; + +<# + var formats = new []{ + "Bmp", + "Gif", + "Jpeg", + "Png", + "Tga", + }; + + foreach (string fmt in formats) + { +#> +using SixLabors.ImageSharp.Formats.<#= fmt #>; +<# + + } +#> + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { +<# + foreach (string fmt in formats) + { +#> + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path, CancellationToken cancellationToken) + => SaveAs<#= fmt #>Async(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder) + => SaveAs<#= fmt #>Async(source, path, encoder, default); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAs<#= fmt #>(this Image source, Stream stream) + => SaveAs<#= fmt #>(source, stream, null); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream) => SaveAs<#= fmt #>Async(source, stream, null); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken) + => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder) + => SaveAs<#= fmt #>Async(source, stream, encoder, default); + + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), + cancellationToken); + +<# + } +#> + } +} diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs deleted file mode 100644 index d6600b625..000000000 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Jpeg; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream) => SaveAsJpeg(source, stream, null); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream) => SaveAsJpegAsync(source, stream, null); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - } -} diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs deleted file mode 100644 index e6a5265b2..000000000 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Png; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream) => SaveAsPng(source, stream, null); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream) => SaveAsPngAsync(source, stream, null); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - } -} diff --git a/src/ImageSharp/Formats/Tga/ImageExtensions.cs b/src/ImageSharp/Formats/Tga/ImageExtensions.cs deleted file mode 100644 index f39738eae..000000000 --- a/src/ImageSharp/Formats/Tga/ImageExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Formats.Tga; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream) => SaveAsTga(source, stream, null); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream) => SaveAsTgaAsync(source, stream, null); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) => - source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - } -} diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index b227679a6..5430810b2 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -35,6 +36,17 @@ namespace SixLabors.ImageSharp public static Task SaveAsync(this Image source, string path) => source.SaveAsync(path, source.DetectEncoder(path)); + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The source image. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// The path is null. + /// A representing the asynchronous operation. + public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken) + => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); + /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -62,14 +74,29 @@ namespace SixLabors.ImageSharp /// The path is null. /// The encoder is null. /// A representing the asynchronous operation. - public static async Task SaveAsync(this Image source, string path, IImageEncoder encoder) + public static Task SaveAsync(this Image source, string path, IImageEncoder encoder) + => SaveAsync(source, path, encoder, default); + + /// + /// Writes the image to the given stream using the currently loaded image format. + /// + /// The source image. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// The path is null. + /// The encoder is null. + /// A representing the asynchronous operation. + public static async Task SaveAsync( + this Image source, + string path, + IImageEncoder encoder, + CancellationToken cancellationToken) { Guard.NotNull(path, nameof(path)); Guard.NotNull(encoder, nameof(encoder)); - using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) - { - await source.SaveAsync(fs, encoder).ConfigureAwait(false); - } + using Stream fs = source.GetConfiguration().FileSystem.Create(path); + await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 97a2a332d..8af404525 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -134,6 +134,11 @@ True PorterDuffFunctions.Generated.tt + + True + True + ImageExtesions.Save.tt + @@ -209,6 +214,10 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator + + TextTemplatingFileGenerator + ImageExtesions.Save.cs + From a627488d79f435f5e9553d7f83e45ea898ea6271 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 02:42:43 +0200 Subject: [PATCH 13/33] implement JpegEncoder cancellation --- .../Formats/Jpeg/JpegEncoderCore.cs | 18 ++++++++---- .../Formats/Jpg/JpegEncoderTests.cs | 28 +++++++++++++++++++ .../Image/ImageTests.SaveAsync.cs | 20 +++++++++++-- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index acc639eb7..593fe9277 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -201,6 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + cancellationToken.ThrowIfCancellationRequested(); const ushort max = JpegConstants.MaxLength; if (image.Width >= max || image.Height >= max) @@ -249,7 +250,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteDefineHuffmanTables(componentCount); // Write the image data. - this.WriteStartOfScan(image); + this.WriteStartOfScan(image, cancellationToken); // Write the End Of Image marker. this.buffer[0] = JpegConstants.Markers.XFF; @@ -398,7 +399,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode444(Image pixels) + /// The token to monitor for cancellation. + private void Encode444(Image pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -420,6 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg for (int y = 0; y < pixels.Height; y += 8) { + cancellationToken.ThrowIfCancellationRequested(); var currentRows = new RowOctet(pixelBuffer, y); for (int x = 0; x < pixels.Width; x += 8) @@ -945,7 +948,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void WriteStartOfScan(Image image) + /// The token to monitor for cancellation. + private void WriteStartOfScan(Image image, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -955,10 +959,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg switch (this.subsample) { case JpegSubsample.Ratio444: - this.Encode444(image); + this.Encode444(image, cancellationToken); break; case JpegSubsample.Ratio420: - this.Encode420(image); + this.Encode420(image, cancellationToken); break; } @@ -972,7 +976,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode420(Image pixels) + /// The token to monitor for cancellation. + private void Encode420(Image pixels, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -1000,6 +1005,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg for (int y = 0; y < pixels.Height; y += 16) { + cancellationToken.ThrowIfCancellationRequested(); for (int x = 0; x < pixels.Width; x += 16) { for (int i = 0; i < 4; i++) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 6c9a74463..11d64e49f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -284,5 +287,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg IccProfile values = input.Metadata.IccProfile; Assert.Equal(values.Entries, actual.Entries); } + + [Theory] + [InlineData(JpegSubsample.Ratio420, 0)] + [InlineData(JpegSubsample.Ratio420, 3)] + [InlineData(JpegSubsample.Ratio420, 10)] + [InlineData(JpegSubsample.Ratio444, 0)] + [InlineData(JpegSubsample.Ratio444, 3)] + [InlineData(JpegSubsample.Ratio444, 10)] + public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs) + { + using var image = new Image(5000, 5000); + using MemoryStream stream = new MemoryStream(); + var cts = new CancellationTokenSource(); + if (cancellationDelayMs == 0) + { + cts.Cancel(); + } + else + { + cts.CancelAfter(cancellationDelayMs); + } + + var encoder = new JpegEncoder() { Subsample = subsample }; + await Assert.ThrowsAnyAsync(() => image.SaveAsync(stream, encoder, cts.Token)); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 4a6c96ae8..de819570f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -3,9 +3,9 @@ using System; using System.IO; - +using System.Threading; using Moq; - +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests [InlineData("test.bmp")] [InlineData("test.jpg")] [InlineData("test.gif")] - public async Task SaveNeverCallsSyncMethods(string filename) + public async Task SaveAsync_NeverCallsSyncMethods(string filename) { using (var image = new Image(5, 5)) { @@ -102,6 +102,20 @@ namespace SixLabors.ImageSharp.Tests } } } + + [Fact] + public async Task SaveAsync_WithNonSeekableStream_IsCancellable() + { + using var image = new Image(500, 500); + IImageEncoder encoder = new BmpEncoder(); + using var stream = new MemoryStream(); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromTicks(1)); + + await Assert.ThrowsAnyAsync(() => + image.SaveAsync(asyncStream, encoder, cts.Token)); + } } } } From 8b179dd6645a6ca7fc3a9414140bdf8d6145aee0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 03:40:02 +0200 Subject: [PATCH 14/33] undo local hacks --- src/ImageSharp/ImageSharp.csproj | 5 ++--- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 8af404525..8b2bbd701 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,8 +10,7 @@ $(packageversion) 0.0.1 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 true true @@ -25,7 +24,7 @@ - + diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 97fd1743d..0761b0978 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,8 +2,7 @@ - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 True True SixLabors.ImageSharp.Tests From 89703bc6fbb19683eb0e62b0dd0cbbf8622b0c40 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 03:40:31 +0200 Subject: [PATCH 15/33] fix Framework build --- .../TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs index 14f5a9c0e..e980ce1e8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() { using Stream stream = this.CreateTestStream(); - int read = await stream.ReadAsync(this.buffer); + int read = await stream.ReadAsync(this.buffer, 0, this.buffer.Length); Assert.Equal(this.buffer.Length, read); } @@ -60,10 +60,10 @@ namespace SixLabors.ImageSharp.Tests public async Task ReadAsync_AfterWaitLimit_ShouldPause() { using Stream stream = this.CreateTestStream(); - await stream.ReadAsync(this.buffer); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); Task readTask = - Task.Factory.StartNew(() => stream.ReadAsync(new byte[512]).AsTask(), TaskCreationOptions.LongRunning); + Task.Factory.StartNew(() => stream.ReadAsync(new byte[512], 0, this.buffer.Length), TaskCreationOptions.LongRunning); await Task.Delay(5); Assert.False(readTask.IsCompleted); await this.notifyWaitPositionReachedSemaphore.WaitAsync(); From b071cc0010571db2f4637bbee2073f3b85915370 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 03:46:18 +0200 Subject: [PATCH 16/33] fix bug in ImageExtensions --- src/ImageSharp/Formats/ImageExtesions.Save.cs | 10 +++++----- src/ImageSharp/Formats/ImageExtesions.Save.tt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.cs b/src/ImageSharp/Formats/ImageExtesions.Save.cs index 1cf5bf2a7..a48007d4b 100644 --- a/src/ImageSharp/Formats/ImageExtesions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtesions.Save.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. // @@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), cancellationToken); /// @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), cancellationToken); /// @@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), cancellationToken); /// @@ -619,7 +619,7 @@ namespace SixLabors.ImageSharp public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), cancellationToken); /// diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.tt b/src/ImageSharp/Formats/ImageExtesions.Save.tt index 37aaf5bb7..69ee4651a 100644 --- a/src/ImageSharp/Formats/ImageExtesions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtesions.Save.tt @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken) => source.SaveAsync( path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), cancellationToken); /// From 4fb21bb255c1b7a55fac37861a75b507ee125a04 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 04:02:35 +0200 Subject: [PATCH 17/33] fix tests --- .../Formats/Jpg/JpegDecoderTests.cs | 4 ++-- .../ImageSharp.Tests/Image/ImageTests.SaveAsync.cs | 4 ++-- .../Tests/SemaphoreReadMemoryStreamTests.cs | 14 +++++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 086798cef..00fb4fcb7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform)] - [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518)] + [InlineData(TestImages.Jpeg.Baseline.Exif)] + [InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)] public async Task Identify_IsCancellable(string fileName) { string hugeFile = Path.Combine( diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index de819570f..3a0294bd3 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -106,8 +106,8 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task SaveAsync_WithNonSeekableStream_IsCancellable() { - using var image = new Image(500, 500); - IImageEncoder encoder = new BmpEncoder(); + using var image = new Image(2000, 2000); + var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; using var stream = new MemoryStream(); var asyncStream = new AsyncStreamWrapper(stream, () => false); var cts = new CancellationTokenSource(); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs index e980ce1e8..87f8cb8c1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -36,6 +36,7 @@ namespace SixLabors.ImageSharp.Tests { using Stream stream = this.CreateTestStream(); stream.Read(this.buffer); + Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); Task readTask = Task.Factory.StartNew( () => @@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests stream.Read(this.buffer); stream.Read(this.buffer); }, TaskCreationOptions.LongRunning); - Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); + await Task.Delay(5); Assert.False(readTask.IsCompleted); await this.notifyWaitPositionReachedSemaphore.WaitAsync(); @@ -62,8 +63,15 @@ namespace SixLabors.ImageSharp.Tests using Stream stream = this.CreateTestStream(); await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - Task readTask = - Task.Factory.StartNew(() => stream.ReadAsync(new byte[512], 0, this.buffer.Length), TaskCreationOptions.LongRunning); + Task readTask = Task.Factory.StartNew( + async () => + { + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + }, TaskCreationOptions.LongRunning); await Task.Delay(5); Assert.False(readTask.IsCompleted); await this.notifyWaitPositionReachedSemaphore.WaitAsync(); From a4ed2f9e6428fdc2e28bede6faa03e3faa910b3a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 26 Jul 2020 04:08:21 +0200 Subject: [PATCH 18/33] skip JpegTests.Identify_IsCancellable --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 00fb4fcb7..78218aec9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -154,18 +154,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg await Assert.ThrowsAsync(() => Image.LoadAsync(hugeFile, cts.Token)); } - [Theory] + [Theory(Skip = "Identify is too fast, doesn't work reliably.")] [InlineData(TestImages.Jpeg.Baseline.Exif)] [InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)] public async Task Identify_IsCancellable(string fileName) { - string hugeFile = Path.Combine( + string file = Path.Combine( TestEnvironment.InputImagesDirectoryFullPath, fileName); var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromTicks(1)); - await Assert.ThrowsAsync(() => Image.IdentifyAsync(hugeFile, cts.Token)); + await Assert.ThrowsAsync(() => Image.IdentifyAsync(file, cts.Token)); } // DEBUG ONLY! From 30ba65964f7f163612def2bd8fbbb08daf967533 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 31 Jul 2020 01:41:00 +0200 Subject: [PATCH 19/33] OperationCanceledException -> TaskCanceledException --- .../Formats/ImageEncoderUtilities.cs | 22 +++++++++++++++++-- .../Formats/Jpg/JpegEncoderTests.cs | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs index 29f0a14c3..6be58bce9 100644 --- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -21,16 +22,33 @@ namespace SixLabors.ImageSharp.Formats Configuration configuration = image.GetConfiguration(); if (stream.CanSeek) { - encoder.Encode(image, stream, cancellationToken); + await DoEncodeAsync(); } else { using var ms = new MemoryStream(); - encoder.Encode(image, ms, cancellationToken); + await DoEncodeAsync(); ms.Position = 0; await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) .ConfigureAwait(false); } + + Task DoEncodeAsync() + { + try + { + encoder.Encode(image, stream, cancellationToken); + return Task.CompletedTask; + } + catch (OperationCanceledException) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } } public static void Encode( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 11d64e49f..981270a5f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -310,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } var encoder = new JpegEncoder() { Subsample = subsample }; - await Assert.ThrowsAnyAsync(() => image.SaveAsync(stream, encoder, cts.Token)); + await Assert.ThrowsAsync(() => image.SaveAsync(stream, encoder, cts.Token)); } } } From 0bddd3bea461f84ecd795224aeca77191da5b846 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 31 Jul 2020 02:13:59 +0200 Subject: [PATCH 20/33] oops the MemoryStream --- src/ImageSharp/Formats/ImageEncoderUtilities.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs index 6be58bce9..b8365368b 100644 --- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -22,22 +22,22 @@ namespace SixLabors.ImageSharp.Formats Configuration configuration = image.GetConfiguration(); if (stream.CanSeek) { - await DoEncodeAsync(); + await DoEncodeAsync(stream); } else { using var ms = new MemoryStream(); - await DoEncodeAsync(); + await DoEncodeAsync(ms); ms.Position = 0; await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) .ConfigureAwait(false); } - Task DoEncodeAsync() + Task DoEncodeAsync(Stream innerStream) { try { - encoder.Encode(image, stream, cancellationToken); + encoder.Encode(image, innerStream, cancellationToken); return Task.CompletedTask; } catch (OperationCanceledException) From 51ddf911373ec70991ba2d57b52f67599923b608 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 31 Jul 2020 02:32:25 +0200 Subject: [PATCH 21/33] try making SaveAsync_WithNonSeekableStream_IsCancellable more robust --- tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 3a0294bd3..40c3b65b5 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task SaveAsync_WithNonSeekableStream_IsCancellable() { - using var image = new Image(2000, 2000); + using var image = new Image(4000, 4000); var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; using var stream = new MemoryStream(); var asyncStream = new AsyncStreamWrapper(stream, () => false); From 643c0b6054ea21ae37327d95f7f87c2813cbb728 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Aug 2020 13:41:19 +0100 Subject: [PATCH 22/33] Move kernels to reusable struct --- src/ImageSharp/ImageSharp.csproj | 2 +- .../Processing/EdgeDetectionOperators.cs | 61 ------- .../Convolution/DetectEdgesExtensions.cs | 30 ++-- .../Processing/KnownEdgeDetectionOperators.cs | 63 +++++++ .../EdgeDetectorCompassProcessor{TPixel}.cs | 27 ++- .../EdgeDetectorProcessor{TPixel}.cs | 24 +-- .../Convolution/Kernels/CompassKernels.cs | 55 ------ .../Kernels/EdgeDetector2DKernel.cs | 103 +++++++++++ .../Kernels/EdgeDetectorCompassKernel.cs | 161 ++++++++++++++++++ .../Convolution/Kernels/EdgeDetectorKernel.cs | 75 ++++++++ .../{ => Implementation}/KayyaliKernels.cs | 0 .../{ => Implementation}/KirschKernels.cs | 22 +-- .../LaplacianKernelFactory.cs | 0 .../{ => Implementation}/LaplacianKernels.cs | 0 .../{ => Implementation}/PrewittKernels.cs | 0 .../RobertsCrossKernels.cs | 0 .../{ => Implementation}/RobinsonKernels.cs | 22 +-- .../{ => Implementation}/ScharrKernels.cs | 0 .../{ => Implementation}/SobelKernels.cs | 0 .../Samplers/DetectEdges.cs | 22 +-- .../Processing/Convolution/DetectEdgesTest.cs | 24 +-- .../Processors/Convolution/DetectEdgesTest.cs | 26 +-- 22 files changed, 497 insertions(+), 220 deletions(-) delete mode 100644 src/ImageSharp/Processing/EdgeDetectionOperators.cs create mode 100644 src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/KayyaliKernels.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/KirschKernels.cs (80%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/LaplacianKernelFactory.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/LaplacianKernels.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/PrewittKernels.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/RobertsCrossKernels.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/RobinsonKernels.cs (80%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/ScharrKernels.cs (100%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{ => Implementation}/SobelKernels.cs (100%) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 64a496141..41ecee503 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/ImageSharp/Processing/EdgeDetectionOperators.cs b/src/ImageSharp/Processing/EdgeDetectionOperators.cs deleted file mode 100644 index 3e986f802..000000000 --- a/src/ImageSharp/Processing/EdgeDetectionOperators.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Enumerates the various types of defined edge detection filters. - /// - public enum EdgeDetectionOperators - { - /// - /// The Kayyali operator filter. - /// - Kayyali, - - /// - /// The Kirsch operator filter. - /// - Kirsch, - - /// - /// The Laplacian3X3 operator filter. - /// - Laplacian3x3, - - /// - /// The Laplacian5X5 operator filter. - /// - Laplacian5x5, - - /// - /// The LaplacianOfGaussian operator filter. - /// - LaplacianOfGaussian, - - /// - /// The Prewitt operator filter. - /// - Prewitt, - - /// - /// The RobertsCross operator filter. - /// - RobertsCross, - - /// - /// The Robinson operator filter. - /// - Robinson, - - /// - /// The Scharr operator filter. - /// - Scharr, - - /// - /// The Sobel operator filter. - /// - Sobel - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index 61b900848..f30d8ad57 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectionOperators filter) => + KnownEdgeDetectionOperators filter) => DetectEdges(source, GetProcessor(filter, true)); /// @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectionOperators filter, + KnownEdgeDetectionOperators filter, bool grayscale) => DetectEdges(source, GetProcessor(filter, grayscale)); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - EdgeDetectionOperators filter, + KnownEdgeDetectionOperators filter, Rectangle rectangle, bool grayscale = true) => DetectEdges(source, rectangle, GetProcessor(filter, grayscale)); @@ -102,45 +102,45 @@ namespace SixLabors.ImageSharp.Processing return source; } - private static IImageProcessor GetProcessor(EdgeDetectionOperators filter, bool grayscale) + private static IImageProcessor GetProcessor(KnownEdgeDetectionOperators filter, bool grayscale) { IImageProcessor processor; switch (filter) { - case EdgeDetectionOperators.Kayyali: + case KnownEdgeDetectionOperators.Kayyali: processor = new KayyaliProcessor(grayscale); break; - case EdgeDetectionOperators.Kirsch: + case KnownEdgeDetectionOperators.Kirsch: processor = new KirschProcessor(grayscale); break; - case EdgeDetectionOperators.Laplacian3x3: + case KnownEdgeDetectionOperators.Laplacian3x3: processor = new Laplacian3x3Processor(grayscale); break; - case EdgeDetectionOperators.Laplacian5x5: + case KnownEdgeDetectionOperators.Laplacian5x5: processor = new Laplacian5x5Processor(grayscale); break; - case EdgeDetectionOperators.LaplacianOfGaussian: + case KnownEdgeDetectionOperators.LaplacianOfGaussian: processor = new LaplacianOfGaussianProcessor(grayscale); break; - case EdgeDetectionOperators.Prewitt: + case KnownEdgeDetectionOperators.Prewitt: processor = new PrewittProcessor(grayscale); break; - case EdgeDetectionOperators.RobertsCross: + case KnownEdgeDetectionOperators.RobertsCross: processor = new RobertsCrossProcessor(grayscale); break; - case EdgeDetectionOperators.Robinson: + case KnownEdgeDetectionOperators.Robinson: processor = new RobinsonProcessor(grayscale); break; - case EdgeDetectionOperators.Scharr: + case KnownEdgeDetectionOperators.Scharr: processor = new ScharrProcessor(grayscale); break; @@ -152,4 +152,4 @@ namespace SixLabors.ImageSharp.Processing return processor; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs b/src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs new file mode 100644 index 000000000..e41fb00ce --- /dev/null +++ b/src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains reusable static instances of known edge detection kernels. + /// + public static class KnownEdgeDetectionOperators + { + /// + /// Gets the Kayyali edge detector kernel. + /// + public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel; + + /// + /// Gets the Kirsch edge detector kernel. + /// + public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch; + + /// + /// Gets the Laplacian 3x3 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3; + + /// + /// Gets the Laplacian 5x5 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5; + + /// + /// Gets the Laplacian of Gaussian edge detector kernel. + /// + public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian; + + /// + /// Gets the Prewitt edge detector kernel. + /// + public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel; + + /// + /// Gets the Roberts-Cross edge detector kernel. + /// + public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel; + + /// + /// Gets the Robinson edge detector kernel. + /// + public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson; + + /// + /// Gets the Scharr edge detector kernel. + /// + public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel; + + /// + /// Gets the Sobel edge detector kernel. + /// + public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 164488155..14d728814 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -18,25 +18,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetectorCompassProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly DenseMatrix[] kernels; + private readonly bool grayscale; + /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// Gets the kernels to use. + /// The kernels to use. /// Whether to convert the image to grayscale before performing edge detection. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetectorCompassProcessor(Configuration configuration, CompassKernels kernels, bool grayscale, Image source, Rectangle sourceRectangle) + internal EdgeDetectorCompassProcessor(Configuration configuration, in EdgeDetectorCompassKernel kernels, bool grayscale, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.Grayscale = grayscale; - this.Kernels = kernels; + this.grayscale = grayscale; + this.kernels = kernels.Flatten(); } - private CompassKernels Kernels { get; } - - private bool Grayscale { get; } - /// protected override void BeforeImageApply() { @@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution opaque.Execute(); } - if (this.Grayscale) + if (this.grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } @@ -56,29 +55,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix[] kernels = this.Kernels.Flatten(); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We need a clean copy for each pass to start from using ImageFrame cleanCopy = source.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest)) + using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[0], true, this.Source, interest)) { processor.Apply(source); } - if (kernels.Length == 1) + if (this.kernels.Length == 1) { return; } // Additional runs - for (int i = 1; i < kernels.Length; i++) + for (int i = 1; i < this.kernels.Length; i++) { using ImageFrame pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) + using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) { processor.Apply(pass); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index 45639d93a..e56e0d1a7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -13,6 +13,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetectorProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly bool grayscale; + private readonly DenseMatrix kernelXY; + /// /// Initializes a new instance of the class. /// @@ -23,23 +26,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The target area to process for the current processor instance. public EdgeDetectorProcessor( Configuration configuration, - in DenseMatrix kernelXY, + in EdgeDetectorKernel kernelXY, bool grayscale, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.KernelXY = kernelXY; - this.Grayscale = grayscale; + this.kernelXY = kernelXY.KernelXY; + this.grayscale = grayscale; } - public bool Grayscale { get; } - - /// - /// Gets the 2d gradient operator. - /// - public DenseMatrix KernelXY { get; } - /// protected override void BeforeImageApply() { @@ -48,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution opaque.Execute(); } - if (this.Grayscale) + if (this.grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } @@ -59,10 +55,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new ConvolutionProcessor(this.Configuration, this.KernelXY, true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs deleted file mode 100644 index 24caa40f8..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/CompassKernels.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - internal abstract class CompassKernels - { - /// - /// Gets the North gradient operator. - /// - public abstract DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator. - /// - public abstract DenseMatrix NorthWest { get; } - - /// - /// Gets the West gradient operator. - /// - public abstract DenseMatrix West { get; } - - /// - /// Gets the SouthWest gradient operator. - /// - public abstract DenseMatrix SouthWest { get; } - - /// - /// Gets the South gradient operator. - /// - public abstract DenseMatrix South { get; } - - /// - /// Gets the SouthEast gradient operator. - /// - public abstract DenseMatrix SouthEast { get; } - - /// - /// Gets the East gradient operator. - /// - public abstract DenseMatrix East { get; } - - /// - /// Gets the NorthEast gradient operator. - /// - public abstract DenseMatrix NorthEast { get; } - - public DenseMatrix[] Flatten() => - new[] - { - this.North, this.NorthWest, this.West, this.SouthWest, - this.South, this.SouthEast, this.East, this.NorthEast - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs new file mode 100644 index 000000000..ed363595d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Represents an edge detection convolution kernel consisting of two 1D gradient operators. + /// + public readonly struct EdgeDetector2DKernel : IEquatable + { + /// + /// An edge detection kernel containing two Kayyali operators. + /// + public static EdgeDetector2DKernel KayyaliKernel = new EdgeDetector2DKernel(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); + + /// + /// An edge detection kernel containing two Prewitt operators. + /// . + /// + public static EdgeDetector2DKernel PrewittKernel = new EdgeDetector2DKernel(PrewittKernels.PrewittX, PrewittKernels.PrewittY); + + /// + /// An edge detection kernel containing two Roberts-Cross operators. + /// . + /// + public static EdgeDetector2DKernel RobertsCrossKernel = new EdgeDetector2DKernel(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); + + /// + /// An edge detection kernel containing two Scharr operators. + /// + public static EdgeDetector2DKernel ScharrKernel = new EdgeDetector2DKernel(ScharrKernels.ScharrX, ScharrKernels.ScharrY); + + /// + /// An edge detection kernel containing two Sobel operators. + /// . + /// + public static EdgeDetector2DKernel SobelKernel = new EdgeDetector2DKernel(SobelKernels.SobelX, SobelKernels.SobelY); + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + public EdgeDetector2DKernel(DenseMatrix kernelX, DenseMatrix kernelY) + { + Guard.IsTrue( + kernelX.Size.Equals(kernelY.Size), + $"{nameof(kernelX)} {nameof(kernelY)}", + "Kernel sizes must be the same."); + + this.KernelX = kernelX; + this.KernelY = kernelY; + } + + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) + => obj is EdgeDetector2DKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetector2DKernel other) + => this.KernelX.Equals(other.KernelX) + && this.KernelY.Equals(other.KernelY); + + /// + public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs new file mode 100644 index 000000000..34fca9192 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs @@ -0,0 +1,161 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Represents an edge detection convolution kernel consisting of eight gradient operators. + /// + public readonly struct EdgeDetectorCompassKernel : IEquatable + { + /// + /// An edge detection kenel comprised of Kirsch gradient operators. + /// + public static EdgeDetectorCompassKernel Kirsch = + new EdgeDetectorCompassKernel( + KirschKernels.North, + KirschKernels.NorthWest, + KirschKernels.West, + KirschKernels.SouthWest, + KirschKernels.South, + KirschKernels.SouthEast, + KirschKernels.East, + KirschKernels.NorthEast); + + /// + /// An edge detection kenel comprised of Robinson gradient operators. + /// + public static EdgeDetectorCompassKernel Robinson = + new EdgeDetectorCompassKernel( + RobinsonKernels.North, + RobinsonKernels.NorthWest, + RobinsonKernels.West, + RobinsonKernels.SouthWest, + RobinsonKernels.South, + RobinsonKernels.SouthEast, + RobinsonKernels.East, + RobinsonKernels.NorthEast); + + /// + /// Initializes a new instance of the struct. + /// + /// The north gradient operator. + /// The north-west gradient operator. + /// The west gradient operator. + /// The south-west gradient operator. + /// The south gradient operator. + /// The south-east gradient operator. + /// The east gradient operator. + /// The north-east gradient operator. + public EdgeDetectorCompassKernel( + DenseMatrix north, + DenseMatrix northWest, + DenseMatrix west, + DenseMatrix southWest, + DenseMatrix south, + DenseMatrix southEast, + DenseMatrix east, + DenseMatrix northEast) + { + this.North = north; + this.NorthWest = northWest; + this.West = west; + this.SouthWest = southWest; + this.South = south; + this.SouthEast = southEast; + this.East = east; + this.NorthEast = northEast; + } + + /// + /// Gets the North gradient operator. + /// + public DenseMatrix North { get; } + + /// + /// Gets the NorthWest gradient operator. + /// + public DenseMatrix NorthWest { get; } + + /// + /// Gets the West gradient operator. + /// + public DenseMatrix West { get; } + + /// + /// Gets the SouthWest gradient operator. + /// + public DenseMatrix SouthWest { get; } + + /// + /// Gets the South gradient operator. + /// + public DenseMatrix South { get; } + + /// + /// Gets the SouthEast gradient operator. + /// + public DenseMatrix SouthEast { get; } + + /// + /// Gets the East gradient operator. + /// + public DenseMatrix East { get; } + + /// + /// Gets the NorthEast gradient operator. + /// + public DenseMatrix NorthEast { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.North, + this.NorthWest, + this.West, + this.SouthWest, + this.South, + this.SouthEast, + this.East, + this.NorthEast); + + internal DenseMatrix[] Flatten() => + new[] + { + this.North, this.NorthWest, this.West, this.SouthWest, + this.South, this.SouthEast, this.East, this.NorthEast + }; + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs new file mode 100644 index 000000000..7b1f91263 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Represents an edge detection convolution kernel consisting of a single 2D gradient operator. + /// + public readonly struct EdgeDetectorKernel : IEquatable + { + /// + /// An edge detection kernel containing a 3x3 Laplacian operator. + /// + public static EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3); + + /// + /// An edge detection kernel containing a 5x5 Laplacian operator. + /// + public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5); + + /// + /// An edge detection kernel containing a Laplacian of Gaussian operator. + /// + public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(LaplacianKernels.LaplacianOfGaussianXY); + + /// + /// Initializes a new instance of the struct. + /// + /// The 2D gradient operator. + public EdgeDetectorKernel(DenseMatrix kernelXY) + => this.KernelXY = kernelXY; + + /// + /// Gets the 2D gradient operator. + /// + public DenseMatrix KernelXY { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) + => obj is EdgeDetectorKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetectorKernel other) + => this.KernelXY.Equals(other.KernelXY); + + /// + public override int GetHashCode() => this.KernelXY.GetHashCode(); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KayyaliKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs index 28c5590ef..ccd8a6c8d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KirschKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Contains the eight matrices used for Kirsch edge detection /// - internal class KirschKernels : CompassKernels + internal static class KirschKernels { /// /// Gets the North gradient operator /// - public override DenseMatrix North => + public static DenseMatrix North => new float[,] { { 5, 5, 5 }, @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public override DenseMatrix NorthWest => + public static DenseMatrix NorthWest => new float[,] { { 5, 5, -3 }, @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public override DenseMatrix West => + public static DenseMatrix West => new float[,] { { 5, -3, -3 }, @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public override DenseMatrix SouthWest => + public static DenseMatrix SouthWest => new float[,] { { -3, -3, -3 }, @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public override DenseMatrix South => + public static DenseMatrix South => new float[,] { { -3, -3, -3 }, @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public override DenseMatrix SouthEast => + public static DenseMatrix SouthEast => new float[,] { { -3, -3, -3 }, @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public override DenseMatrix East => + public static DenseMatrix East => new float[,] { { -3, -3, 5 }, @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public override DenseMatrix NorthEast => + public static DenseMatrix NorthEast => new float[,] { { -3, 5, 5 }, @@ -96,4 +96,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { -3, -3, -3 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernelFactory.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/LaplacianKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/PrewittKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/RobertsCrossKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs similarity index 80% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs index 857c772b0..0dde36f02 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/RobinsonKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Contains the kernels used for Robinson edge detection. /// - internal class RobinsonKernels : CompassKernels + internal static class RobinsonKernels { /// /// Gets the North gradient operator /// - public override DenseMatrix North => + public static DenseMatrix North => new float[,] { { 1, 2, 1 }, @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthWest gradient operator /// - public override DenseMatrix NorthWest => + public static DenseMatrix NorthWest => new float[,] { { 2, 1, 0 }, @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the West gradient operator /// - public override DenseMatrix West => + public static DenseMatrix West => new float[,] { { 1, 0, -1 }, @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthWest gradient operator /// - public override DenseMatrix SouthWest => + public static DenseMatrix SouthWest => new float[,] { { 0, -1, -2 }, @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the South gradient operator /// - public override DenseMatrix South => + public static DenseMatrix South => new float[,] { { -1, -2, -1 }, @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the SouthEast gradient operator /// - public override DenseMatrix SouthEast => + public static DenseMatrix SouthEast => new float[,] { { -2, -1, 0 }, @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the East gradient operator /// - public override DenseMatrix East => + public static DenseMatrix East => new float[,] { { -1, 0, 1 }, @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Gets the NorthEast gradient operator /// - public override DenseMatrix NorthEast => + public static DenseMatrix NorthEast => new float[,] { { 0, 1, 2 }, @@ -96,4 +96,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { -2, -1, 0 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/ScharrKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/SobelKernels.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index d40201bd8..cf46c4f52 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp DetectEdges")] public void ImageProcessorCoreDetectEdges() { - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kayyali)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Kirsch)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian3x3)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Laplacian5x5)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.LaplacianOfGaussian)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Prewitt)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.RobertsCross)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Robinson)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Scharr)); - this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kirsch)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Laplacian3x3)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Laplacian5x5)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.LaplacianOfGaussian)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Prewitt)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.RobertsCross)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Robinson)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Scharr)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Sobel)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index a0e9be110..fd41a04e2 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -35,21 +35,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public static IEnumerable EdgeDetectionTheoryData => new[] { - new object[] { new TestType(), EdgeDetectionOperators.Kayyali }, - new object[] { new TestType(), EdgeDetectionOperators.Kirsch }, - new object[] { new TestType(), EdgeDetectionOperators.Laplacian3x3 }, - new object[] { new TestType(), EdgeDetectionOperators.Laplacian5x5 }, - new object[] { new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, - new object[] { new TestType(), EdgeDetectionOperators.Prewitt }, - new object[] { new TestType(), EdgeDetectionOperators.RobertsCross }, - new object[] { new TestType(), EdgeDetectionOperators.Robinson }, - new object[] { new TestType(), EdgeDetectionOperators.Scharr }, - new object[] { new TestType(), EdgeDetectionOperators.Sobel }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Kayyali }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Kirsch }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Laplacian3x3 }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Laplacian5x5 }, + new object[] { new TestType(), KnownEdgeDetectionOperators.LaplacianOfGaussian }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Prewitt }, + new object[] { new TestType(), KnownEdgeDetectionOperators.RobertsCross }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Robinson }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Scharr }, + new object[] { new TestType(), KnownEdgeDetectionOperators.Sobel }, }; [Theory] [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) + public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType type, KnownEdgeDetectionOperators filter) where TProcessor : EdgeDetectorProcessor { this.operations.DetectEdges(filter); @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution [Theory] [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, EdgeDetectionOperators filter) + public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, KnownEdgeDetectionOperators filter) where TProcessor : EdgeDetectorProcessor { bool grey = (int)filter % 2 == 0; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index dbbf624db..a96b05d63 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -20,18 +20,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly TheoryData DetectEdgesFilters = new TheoryData + public static readonly TheoryData DetectEdgesFilters = new TheoryData { - EdgeDetectionOperators.Kayyali, - EdgeDetectionOperators.Kirsch, - EdgeDetectionOperators.Laplacian3x3, - EdgeDetectionOperators.Laplacian5x5, - EdgeDetectionOperators.LaplacianOfGaussian, - EdgeDetectionOperators.Prewitt, - EdgeDetectionOperators.RobertsCross, - EdgeDetectionOperators.Robinson, - EdgeDetectionOperators.Scharr, - EdgeDetectionOperators.Sobel + KnownEdgeDetectionOperators.Kayyali, + KnownEdgeDetectionOperators.Kirsch, + KnownEdgeDetectionOperators.Laplacian3x3, + KnownEdgeDetectionOperators.Laplacian5x5, + KnownEdgeDetectionOperators.LaplacianOfGaussian, + KnownEdgeDetectionOperators.Prewitt, + KnownEdgeDetectionOperators.RobertsCross, + KnownEdgeDetectionOperators.Robinson, + KnownEdgeDetectionOperators.Scharr, + KnownEdgeDetectionOperators.Sobel }; [Theory] @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) + public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, KnownEdgeDetectionOperators detector) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectionOperators detector) + public void WorksWithDiscoBuffers(TestImageProvider provider, KnownEdgeDetectionOperators detector) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( From 6485be853cd8962085be3295ff2ac2d815acda53 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Aug 2020 14:15:36 +0100 Subject: [PATCH 23/33] Replace individual processors --- .../Convolution/EdgeDetector2DProcessor.cs | 42 ++++++++++++++++++ .../EdgeDetector2DProcessor{TPixel}.cs | 43 ++++++++----------- .../EdgeDetectorCompassProcessor.cs | 42 ++++++++++++++++++ .../EdgeDetectorCompassProcessor{TPixel}.cs | 13 +++--- .../Convolution/EdgeDetectorProcessor.cs | 25 ++++++++--- .../EdgeDetectorProcessor{TPixel}.cs | 10 ++--- .../Convolution/KayyaliProcessor.cs | 31 ------------- .../Kernels/EdgeDetectorCompassKernel.cs | 2 + .../Convolution/Kernels/EdgeDetectorKernel.cs | 3 ++ .../Kernels/Implementation/KirschKernels.cs | 3 +- .../Implementation/LaplacianKernels.cs | 8 ++-- .../Kernels/Implementation/RobinsonKernels.cs | 1 + .../Processors/Convolution/KirschProcessor.cs | 25 ----------- .../Convolution/Laplacian3x3Processor.cs | 25 ----------- .../Convolution/Laplacian5x5Processor.cs | 25 ----------- .../LaplacianOfGaussianProcessor.cs | 25 ----------- .../Convolution/PrewittProcessor.cs | 31 ------------- .../Convolution/RobertsCrossProcessor.cs | 31 ------------- .../Convolution/RobinsonProcessor.cs | 25 ----------- .../Processors/Convolution/ScharrProcessor.cs | 31 ------------- .../Processors/Convolution/SobelProcessor.cs | 31 ------------- 21 files changed, 145 insertions(+), 327 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs new file mode 100644 index 000000000..1d0a29a35 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Defines edge detection using the two 1D gradient operators. + /// + public sealed class EdgeDetector2DProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale) + { + this.Kernel = kernel; + this.Grayscale = grayscale; + } + + /// + /// Gets the 2D edge detector kernel. + /// + public EdgeDetector2DKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 6c831e727..80f8a7706 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -13,42 +13,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution internal class EdgeDetector2DProcessor : ImageProcessor where TPixel : unmanaged, IPixel { + private readonly DenseMatrix kernelX; + private readonly DenseMatrix kernelY; + private readonly bool grayscale; + /// /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The horizontal gradient operator. - /// The vertical gradient operator. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetector2DProcessor( + public EdgeDetector2DProcessor( Configuration configuration, - in DenseMatrix kernelX, - in DenseMatrix kernelY, - bool grayscale, + EdgeDetector2DProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); - this.KernelX = kernelX; - this.KernelY = kernelY; - this.Grayscale = grayscale; + this.kernelX = definition.Kernel.KernelX; + this.kernelY = definition.Kernel.KernelY; + this.grayscale = definition.Grayscale; } - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } - - public bool Grayscale { get; } - /// protected override void BeforeImageApply() { @@ -57,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution opaque.Execute(); } - if (this.Grayscale) + if (this.grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } @@ -68,7 +55,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); + using var processor = new Convolution2DProcessor( + this.Configuration, + in this.kernelX, + in this.kernelY, + true, + this.Source, + this.SourceRectangle); processor.Apply(source); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs new file mode 100644 index 000000000..083a69bd2 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// Defines edge detection using eight gradient operators. + /// + public sealed class EdgeDetectorCompassProcessor : IImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) + { + this.Kernel = kernel; + this.Grayscale = grayscale; + } + + /// + /// Gets the edge detector kernel. + /// + public EdgeDetectorCompassKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 14d728814..27963613e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -25,15 +25,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The kernels to use. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - internal EdgeDetectorCompassProcessor(Configuration configuration, in EdgeDetectorCompassKernel kernels, bool grayscale, Image source, Rectangle sourceRectangle) + internal EdgeDetectorCompassProcessor( + Configuration configuration, + EdgeDetectorCompassProcessor definition, + Image source, + Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.grayscale = grayscale; - this.kernels = kernels.Flatten(); + this.grayscale = definition.Grayscale; + this.kernels = definition.Kernel.Flatten(); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index a9d20b547..6fcfca662 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -6,26 +6,37 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Defines a processor that detects edges within an image using a single two dimensional matrix. + /// Defines edge detection using a single 2D gradient operator. /// - public abstract class EdgeDetectorProcessor : IImageProcessor + public sealed class EdgeDetectorProcessor : IImageProcessor { /// /// Initializes a new instance of the class. /// - /// A value indicating whether to convert the image to grayscale before performing edge detection. - protected EdgeDetectorProcessor(bool grayscale) + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) { + this.Kernel = kernel; this.Grayscale = grayscale; } /// - /// Gets a value indicating whether to convert the image to grayscale before performing edge detection. + /// Gets the edge detector kernel. + /// + public EdgeDetectorKernel Kernel { get; } + + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. /// public bool Grayscale { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index e56e0d1a7..62dd54919 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -20,20 +20,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The configuration which allows altering default behaviour or extending the library. - /// The 2d gradient operator. - /// Whether to convert the image to grayscale before performing edge detection. + /// The defining the processor parameters. /// The source for the current processor instance. /// The target area to process for the current processor instance. public EdgeDetectorProcessor( Configuration configuration, - in EdgeDetectorKernel kernelXY, - bool grayscale, + EdgeDetectorProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.kernelXY = kernelXY.KernelXY; - this.grayscale = grayscale; + this.kernelXY = definition.Kernel.KernelXY; + this.grayscale = definition.Grayscale; } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs deleted file mode 100644 index c13e8b543..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Kayyali operator filter. - /// See . - /// - public sealed class KayyaliProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public KayyaliProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - KayyaliKernels.KayyaliX, - KayyaliKernels.KayyaliY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs index 34fca9192..bda861799 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// /// An edge detection kenel comprised of Kirsch gradient operators. + /// . /// public static EdgeDetectorCompassKernel Kirsch = new EdgeDetectorCompassKernel( @@ -26,6 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// An edge detection kenel comprised of Robinson gradient operators. + /// /// public static EdgeDetectorCompassKernel Robinson = new EdgeDetectorCompassKernel( diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs index 7b1f91263..86b8a24d9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs @@ -12,16 +12,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// /// An edge detection kernel containing a 3x3 Laplacian operator. + /// /// public static EdgeDetectorKernel Laplacian3x3 = new EdgeDetectorKernel(LaplacianKernels.Laplacian3x3); /// /// An edge detection kernel containing a 5x5 Laplacian operator. + /// /// public static EdgeDetectorKernel Laplacian5x5 = new EdgeDetectorKernel(LaplacianKernels.Laplacian5x5); /// /// An edge detection kernel containing a Laplacian of Gaussian operator. + /// . /// public static EdgeDetectorKernel LaplacianOfGaussian = new EdgeDetectorKernel(LaplacianKernels.LaplacianOfGaussianXY); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs index ccd8a6c8d..87ccb174d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs @@ -4,7 +4,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Contains the eight matrices used for Kirsch edge detection + /// Contains the eight matrices used for Kirsch edge detection. + /// . /// internal static class KirschKernels { diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs index ce574341f..7036300c7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs @@ -1,10 +1,12 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// - /// Contains Laplacian kernels of different sizes + /// Contains Laplacian kernels of different sizes. + /// + /// . /// internal static class LaplacianKernels { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { 0, 0, -1, 0, 0 } }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs index 0dde36f02..7d0478aa5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs @@ -5,6 +5,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { /// /// Contains the kernels used for Robinson edge detection. + /// /// internal static class RobinsonKernels { diff --git a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs deleted file mode 100644 index 62bd17016..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Kirsch operator filter. - /// See . - /// - public sealed class KirschProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public KirschProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorCompassProcessor(configuration, new KirschKernels(), this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs deleted file mode 100644 index 957c61b07..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Applies edge detection processing to the image using the Laplacian 3x3 operator filter. - /// - /// - public sealed class Laplacian3x3Processor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public Laplacian3x3Processor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs deleted file mode 100644 index 8b28662ae..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Laplacian 5x5 operator filter. - /// . - /// - public sealed class Laplacian5x5Processor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public Laplacian5x5Processor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs deleted file mode 100644 index 5c8f7c40d..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Applies edge detection processing to the image using the Laplacian of Gaussian operator filter. - /// See . - /// - public sealed class LaplacianOfGaussianProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public LaplacianOfGaussianProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorProcessor(configuration, LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs deleted file mode 100644 index c5ae14935..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Prewitt operator filter. - /// See . - /// - public sealed class PrewittProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public PrewittProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - PrewittKernels.PrewittX, - PrewittKernels.PrewittY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs deleted file mode 100644 index 57df83a14..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Roberts Cross operator filter. - /// See . - /// - public sealed class RobertsCrossProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public RobertsCrossProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - RobertsCrossKernels.RobertsCrossX, - RobertsCrossKernels.RobertsCrossY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs deleted file mode 100644 index 22c8562e2..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/RobinsonProcessor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Robinson operator filter. - /// See . - /// - public sealed class RobinsonProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public RobinsonProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetectorCompassProcessor(configuration, new RobinsonKernels(), this.Grayscale, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs deleted file mode 100644 index d9a0745e2..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/ScharrProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection processing using the Scharr operator filter. - /// - /// - public sealed class ScharrProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public ScharrProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - ScharrKernels.ScharrX, - ScharrKernels.ScharrY, - this.Grayscale, - source, - sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs deleted file mode 100644 index 73632392e..000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/SobelProcessor.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// Defines edge detection using the Sobel operator filter. - /// See . - /// - public sealed class SobelProcessor : EdgeDetectorProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// Whether to convert the image to grayscale before performing edge detection. - public SobelProcessor(bool grayscale) - : base(grayscale) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new EdgeDetector2DProcessor( - configuration, - SobelKernels.SobelX, - SobelKernels.SobelY, - this.Grayscale, - source, - sourceRectangle); - } -} From a9aa3e5ef6b1d1f8759063d1ba6661cdb580fa81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Aug 2020 21:51:32 +0100 Subject: [PATCH 24/33] Update tests --- .../Convolution/DetectEdgesExtensions.cs | 245 ++++++++++++------ ...erators.cs => KnownEdgeDetectorKernels.cs} | 2 +- .../EntropyCropProcessor{TPixel}.cs | 2 +- .../Samplers/DetectEdges.cs | 22 +- .../Processing/Convolution/DetectEdgesTest.cs | 196 +++++++++++--- .../Processors/Convolution/DetectEdgesTest.cs | 92 ++++++- 6 files changed, 417 insertions(+), 142 deletions(-) rename src/ImageSharp/Processing/{KnownEdgeDetectionOperators.cs => KnownEdgeDetectorKernels.cs} (97%) diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index f30d8ad57..2377151bb 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Processing @@ -12,144 +11,230 @@ namespace SixLabors.ImageSharp.Processing public static class DetectEdgesExtensions { /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. /// /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, new SobelProcessor(true)); + DetectEdges(source, KnownEdgeDetectorKernels.Sobel); /// - /// Detects any edges within the image. Uses the filter - /// operating in grayscale mode. + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. /// /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source, Rectangle rectangle) => - DetectEdges(source, rectangle, new SobelProcessor(true)); + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + Rectangle rectangle) => + DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle); /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - KnownEdgeDetectionOperators filter) => - DetectEdges(source, GetProcessor(filter, true)); + EdgeDetector2DKernel kernel) => + DetectEdges(source, kernel, true); /// - /// Detects any edges within the image. + /// Detects any edges within the image using a . /// /// The image this method extends. - /// The filter for detecting edges. - /// Whether to convert the image to grayscale first. Defaults to true. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - KnownEdgeDetectionOperators filter, - bool grayscale) => - DetectEdges(source, GetProcessor(filter, grayscale)); + EdgeDetector2DKernel kernel, + bool grayscale) + { + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. /// /// The structure that specifies the portion of the image object to alter. /// - /// Whether to convert the image to grayscale first. Defaults to true. /// The to allow chaining of operations. public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - KnownEdgeDetectionOperators filter, - Rectangle rectangle, - bool grayscale = true) => - DetectEdges(source, rectangle, GetProcessor(filter, grayscale)); + EdgeDetector2DKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); /// - /// Detects any edges within the image. + /// Detects any edges within the image using a . /// /// The image this method extends. - /// The filter for detecting edges. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The to allow chaining of operations. - private static IImageProcessingContext DetectEdges(this IImageProcessingContext source, IImageProcessor filter) + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + bool grayscale, + Rectangle rectangle) { - return source.ApplyProcessor(filter); + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; } /// - /// Detects any edges within the image. + /// Detects any edges within the image operating in grayscale mode. /// /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. + /// The edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. /// - /// The filter for detecting edges. /// The to allow chaining of operations. - private static IImageProcessingContext DetectEdges( + public static IImageProcessingContext DetectEdges( this IImageProcessingContext source, - Rectangle rectangle, - IImageProcessor filter) + EdgeDetectorKernel kernel, + bool grayscale) { - source.ApplyProcessor(filter, rectangle); + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor); return source; } - private static IImageProcessor GetProcessor(KnownEdgeDetectionOperators filter, bool grayscale) - { - IImageProcessor processor; - - switch (filter) - { - case KnownEdgeDetectionOperators.Kayyali: - processor = new KayyaliProcessor(grayscale); - break; - - case KnownEdgeDetectionOperators.Kirsch: - processor = new KirschProcessor(grayscale); - break; - - case KnownEdgeDetectionOperators.Laplacian3x3: - processor = new Laplacian3x3Processor(grayscale); - break; - - case KnownEdgeDetectionOperators.Laplacian5x5: - processor = new Laplacian5x5Processor(grayscale); - break; - - case KnownEdgeDetectionOperators.LaplacianOfGaussian: - processor = new LaplacianOfGaussianProcessor(grayscale); - break; - - case KnownEdgeDetectionOperators.Prewitt: - processor = new PrewittProcessor(grayscale); - break; + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); - case KnownEdgeDetectionOperators.RobertsCross: - processor = new RobertsCrossProcessor(grayscale); - break; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } - case KnownEdgeDetectionOperators.Robinson: - processor = new RobinsonProcessor(grayscale); - break; + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel) => + DetectEdges(source, kernel, true); - case KnownEdgeDetectionOperators.Scharr: - processor = new ScharrProcessor(grayscale); - break; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale) + { + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } - default: - processor = new SobelProcessor(grayscale); - break; - } + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); - return processor; + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs similarity index 97% rename from src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs rename to src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs index e41fb00ce..2e279d340 100644 --- a/src/ImageSharp/Processing/KnownEdgeDetectionOperators.cs +++ b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Contains reusable static instances of known edge detection kernels. /// - public static class KnownEdgeDetectionOperators + public static class KnownEdgeDetectorKernels { /// /// Gets the Kayyali edge detector kernel. diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index ebc8f0e4f..dd9c06938 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration = this.Source.GetConfiguration(); // Detect the edges. - new SobelProcessor(false).Execute(this.Configuration, temp, this.SourceRectangle); + new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle); // Apply threshold binarization filter. new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index cf46c4f52..ce2fa988c 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp DetectEdges")] public void ImageProcessorCoreDetectEdges() { - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Kirsch)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Laplacian3x3)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Laplacian5x5)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.LaplacianOfGaussian)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Prewitt)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.RobertsCross)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Robinson)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Scharr)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectionOperators.Sobel)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index fd41a04e2..e14ab748f 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,12 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; - using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using SixLabors.ImageSharp.Tests.TestUtilities; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution @@ -14,62 +10,190 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution public class DetectEdgesTest : BaseImageOperationsExtensionTest { [Fact] - public void DetectEdges_SobelProcessorDefaultsSet() + public void DetectEdges_EdgeDetector2DProcessorDefaultsSet() { this.operations.DetectEdges(); + EdgeDetector2DProcessor processor = this.Verify(); - // TODO: Enable once we have updated the images - SobelProcessor processor = this.Verify(); Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); } [Fact] - public void DetectEdges_Rect_SobelProcessorDefaultsSet() + public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet() { this.operations.DetectEdges(this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); + } + + public static TheoryData EdgeDetector2DKernelData = + new TheoryData + { + { KnownEdgeDetectorKernels.Kayyali, true }, + { KnownEdgeDetectorKernels.Kayyali, false }, + { KnownEdgeDetectorKernels.Prewitt, true }, + { KnownEdgeDetectorKernels.Prewitt, false }, + { KnownEdgeDetectorKernels.RobertsCross, true }, + { KnownEdgeDetectorKernels.RobertsCross, false }, + { KnownEdgeDetectorKernels.Scharr, true }, + { KnownEdgeDetectorKernels.Scharr, false }, + { KnownEdgeDetectorKernels.Sobel, true }, + { KnownEdgeDetectorKernels.Sobel, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetector2DProcessor processor = this.Verify(); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale); + EdgeDetector2DProcessor processor = this.Verify(); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorKernelData = + new TheoryData + { + { KnownEdgeDetectorKernels.Laplacian3x3, true }, + { KnownEdgeDetectorKernels.Laplacian3x3, false }, + { KnownEdgeDetectorKernels.Laplacian5x5, true }, + { KnownEdgeDetectorKernels.Laplacian5x5, false }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, true }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetectorProcessor processor = this.Verify(); - // TODO: Enable once we have updated the images - SobelProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } - public static IEnumerable EdgeDetectionTheoryData => new[] + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) { - new object[] { new TestType(), KnownEdgeDetectionOperators.Kayyali }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Kirsch }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Laplacian3x3 }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Laplacian5x5 }, - new object[] { new TestType(), KnownEdgeDetectionOperators.LaplacianOfGaussian }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Prewitt }, - new object[] { new TestType(), KnownEdgeDetectionOperators.RobertsCross }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Robinson }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Scharr }, - new object[] { new TestType(), KnownEdgeDetectionOperators.Sobel }, - }; + this.operations.DetectEdges(kernel, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } [Theory] - [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_SobelProcessorDefaultsSet(TestType type, KnownEdgeDetectionOperators filter) - where TProcessor : EdgeDetectorProcessor + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) { - this.operations.DetectEdges(filter); + this.operations.DetectEdges(kernel, grayscale); + EdgeDetectorProcessor processor = this.Verify(); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + public static TheoryData EdgeDetectorCompassKernelData = + new TheoryData + { + { KnownEdgeDetectorKernels.Kirsch, true }, + { KnownEdgeDetectorKernels.Kirsch, false }, + { KnownEdgeDetectorKernels.Robinson, true }, + { KnownEdgeDetectorKernels.Robinson, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetectorCompassProcessor processor = this.Verify(); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - // TODO: Enable once we have updated the images - var processor = this.Verify(); Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale); + EdgeDetectorCompassProcessor processor = this.Verify(); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } [Theory] - [MemberData(nameof(EdgeDetectionTheoryData))] - public void DetectEdges_filter_grayscale_SobelProcessorDefaultsSet(TestType type, KnownEdgeDetectionOperators filter) - where TProcessor : EdgeDetectorProcessor + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) { - bool grey = (int)filter % 2 == 0; - this.operations.DetectEdges(filter, grey); + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - // TODO: Enable once we have updated the images - var processor = this.Verify(); - Assert.Equal(grey, processor.Grayscale); + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index a96b05d63..7a3b104b3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -20,18 +21,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly TheoryData DetectEdgesFilters = new TheoryData + public static readonly TheoryData DetectEdgesFilters + = new TheoryData { - KnownEdgeDetectionOperators.Kayyali, - KnownEdgeDetectionOperators.Kirsch, - KnownEdgeDetectionOperators.Laplacian3x3, - KnownEdgeDetectionOperators.Laplacian5x5, - KnownEdgeDetectionOperators.LaplacianOfGaussian, - KnownEdgeDetectionOperators.Prewitt, - KnownEdgeDetectionOperators.RobertsCross, - KnownEdgeDetectionOperators.Robinson, - KnownEdgeDetectionOperators.Scharr, - KnownEdgeDetectionOperators.Sobel + KnownEdgeDetectorKernels.Laplacian3x3, + KnownEdgeDetectorKernels.Laplacian5x5, + KnownEdgeDetectorKernels.LaplacianOfGaussian, + }; + + public static readonly TheoryData DetectEdges2DFilters + = new TheoryData + { + KnownEdgeDetectorKernels.Kayyali, + KnownEdgeDetectorKernels.Prewitt, + KnownEdgeDetectorKernels.RobertsCross, + KnownEdgeDetectorKernels.Scharr, + KnownEdgeDetectorKernels.Sobel + }; + + public static readonly TheoryData DetectEdgesCompassFilters + = new TheoryData + { + KnownEdgeDetectorKernels.Kirsch, + KnownEdgeDetectorKernels.Robinson, }; [Theory] @@ -53,7 +65,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, KnownEdgeDetectionOperators detector) + public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectorKernel detector) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, detector.ToString()); + image.CompareToReferenceOutput(comparer, provider, detector.ToString()); + } + } + + [Theory] + [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] + public void DetectEdges2D_WorksWithAllFilters(TestImageProvider provider, EdgeDetector2DKernel detector) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, detector.ToString()); + image.CompareToReferenceOutput(comparer, provider, detector.ToString()); + } + } + + [Theory] + [WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void DetectEdgesCompass_WorksWithAllFilters(TestImageProvider provider, EdgeDetectorCompassKernel detector) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -115,7 +159,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers(TestImageProvider provider, KnownEdgeDetectionOperators detector) + public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectorKernel detector) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers2D(TestImageProvider provider, EdgeDetector2DKernel detector) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffersCompass(TestImageProvider provider, EdgeDetectorCompassKernel detector) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( From b130bcd5e3d8dd5db5d32305879566ace01d9d43 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Aug 2020 22:37:15 +0100 Subject: [PATCH 25/33] Update skipped tests --- .../Processors/Convolution/DetectEdgesTest.cs | 78 ++++++++++++------- tests/Images/External | 2 +- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 7a3b104b3..33619415b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -21,29 +21,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly TheoryData DetectEdgesFilters - = new TheoryData + public static readonly TheoryData DetectEdgesFilters + = new TheoryData { - KnownEdgeDetectorKernels.Laplacian3x3, - KnownEdgeDetectorKernels.Laplacian5x5, - KnownEdgeDetectorKernels.LaplacianOfGaussian, + { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) }, + { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) }, }; - public static readonly TheoryData DetectEdges2DFilters - = new TheoryData + public static readonly TheoryData DetectEdges2DFilters + = new TheoryData { - KnownEdgeDetectorKernels.Kayyali, - KnownEdgeDetectorKernels.Prewitt, - KnownEdgeDetectorKernels.RobertsCross, - KnownEdgeDetectorKernels.Scharr, - KnownEdgeDetectorKernels.Sobel + { KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) }, + { KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) }, + { KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) }, + { KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) }, + { KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) }, }; - public static readonly TheoryData DetectEdgesCompassFilters - = new TheoryData + public static readonly TheoryData DetectEdgesCompassFilters + = new TheoryData { - KnownEdgeDetectorKernels.Kirsch, - KnownEdgeDetectorKernels.Robinson, + { KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) }, + { KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) }, }; [Theory] @@ -65,7 +65,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectorKernel detector) + public void DetectEdges_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetectorKernel detector, + string name) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -73,15 +76,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, detector.ToString()); - image.CompareToReferenceOutput(comparer, provider, detector.ToString()); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } } [Theory] - [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void DetectEdges2D_WorksWithAllFilters(TestImageProvider provider, EdgeDetector2DKernel detector) + public void DetectEdges2D_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string name) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -89,15 +95,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, detector.ToString()); - image.CompareToReferenceOutput(comparer, provider, detector.ToString()); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } } [Theory] [WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdgesCompass_WorksWithAllFilters(TestImageProvider provider, EdgeDetectorCompassKernel detector) + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] + public void DetectEdgesCompass_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string name) where TPixel : unmanaged, IPixel { bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); @@ -105,8 +114,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges(detector)); - image.DebugSave(provider, detector.ToString()); - image.CompareToReferenceOutput(comparer, provider, detector.ToString()); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } } @@ -159,7 +168,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectorKernel detector) + public void WorksWithDiscoBuffers( + TestImageProvider provider, + EdgeDetectorKernel detector, + string _) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( @@ -170,7 +182,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers2D(TestImageProvider provider, EdgeDetector2DKernel detector) + public void WorksWithDiscoBuffers2D( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string _) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( @@ -181,7 +196,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffersCompass(TestImageProvider provider, EdgeDetectorCompassKernel detector) + public void WorksWithDiscoBuffersCompass( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string _) where TPixel : unmanaged, IPixel { provider.RunBufferCapacityLimitProcessorTest( diff --git a/tests/Images/External b/tests/Images/External index 0d1f91e2f..6a0030806 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 0d1f91e2fe1491f6dc2c137a8ea20460fde4404c +Subproject commit 6a003080674d1fedc66292c13ce5a357b2a33083 From 59139d72e3259ddfd9b5d422eeb3e2abfe8cf09a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 1 Aug 2020 23:22:36 +0100 Subject: [PATCH 26/33] Create EdgeDetectorKernelTests.cs --- .../Processors/EdgeDetectorKernelTests.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs new file mode 100644 index 000000000..b50e5d7cc --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors +{ + public class EdgeDetectorKernelTests + { + [Fact] + public void EdgeDetectorKernelEqualityOperatorTest() + { + EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3; + EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3; + EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5; + + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); + + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); + + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); + + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); + + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); + } + + [Fact] + public void EdgeDetector2DKernelEqualityOperatorTest() + { + EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt; + EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt; + EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross; + + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); + + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); + + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); + + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); + + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); + } + + [Fact] + public void EdgeDetectorCompassKernelEqualityOperatorTest() + { + EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch; + EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch; + EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson; + + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); + + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); + + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); + + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); + + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); + } + } +} From 84e6112b9daf85a79706375ed8ef938afdf4c81d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 2 Aug 2020 11:29:33 +0100 Subject: [PATCH 27/33] Remove warnings --- .../Processing/Processors/Convolution/DetectEdgesTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 33619415b..e468778de 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; @@ -11,6 +12,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { [GroupOutput("Convolution")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] public class DetectEdgesTest { private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); From 9f3ea01c19359afd35de72615d13a4affd0155d0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 2 Aug 2020 12:22:15 +0100 Subject: [PATCH 28/33] Remove all warnings --- .../ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index e14ab748f..3ffb8f4e3 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Convolution { + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "OK. Used for TheoryData compatibility.")] public class DetectEdgesTest : BaseImageOperationsExtensionTest { [Fact] From 7e2d30b93113647df80b78fdb1d7885edd7a29a5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Aug 2020 01:31:00 +0200 Subject: [PATCH 29/33] use default args for CancellationTokens --- .../Advanced/AdvancedImageExtensions.cs | 2 +- src/ImageSharp/Formats/ImageExtesions.Save.cs | 185 ++-------------- src/ImageSharp/Formats/ImageExtesions.Save.tt | 37 +--- src/ImageSharp/Image.Decode.cs | 5 +- src/ImageSharp/Image.FromFile.cs | 158 +++---------- src/ImageSharp/Image.FromStream.cs | 208 +++--------------- src/ImageSharp/Image.cs | 12 +- src/ImageSharp/ImageExtensions.cs | 12 +- 8 files changed, 80 insertions(+), 539 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 19d6334e3..c5abbda61 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Advanced /// The image visitor. /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. - public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken) + public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) => source.AcceptAsync(visitor, cancellationToken); /// diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.cs b/src/ImageSharp/Formats/ImageExtesions.Save.cs index a48007d4b..075c708b6 100644 --- a/src/ImageSharp/Formats/ImageExtesions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtesions.Save.cs @@ -60,17 +60,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder) - => SaveAsBmpAsync(source, path, encoder, default); - /// /// Saves the image to the given stream with the Bmp format. /// @@ -80,7 +69,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), @@ -95,15 +84,6 @@ namespace SixLabors.ImageSharp public static void SaveAsBmp(this Image source, Stream stream) => SaveAsBmp(source, stream, null); - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream) => SaveAsBmpAsync(source, stream, null); - /// /// Saves the image to the given stream with the Bmp format. /// @@ -112,7 +92,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAsBmpAsync(source, stream, null, cancellationToken); /// @@ -128,17 +108,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder) - => SaveAsBmpAsync(source, stream, encoder, default); - /// /// Saves the image to the given stream with the Bmp format. /// @@ -148,7 +117,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), @@ -194,17 +163,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder) - => SaveAsGifAsync(source, path, encoder, default); - /// /// Saves the image to the given stream with the Gif format. /// @@ -214,7 +172,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), @@ -229,15 +187,6 @@ namespace SixLabors.ImageSharp public static void SaveAsGif(this Image source, Stream stream) => SaveAsGif(source, stream, null); - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream) => SaveAsGifAsync(source, stream, null); - /// /// Saves the image to the given stream with the Gif format. /// @@ -246,7 +195,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAsGifAsync(source, stream, null, cancellationToken); /// @@ -262,17 +211,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder) - => SaveAsGifAsync(source, stream, encoder, default); - /// /// Saves the image to the given stream with the Gif format. /// @@ -282,7 +220,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), @@ -328,17 +266,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder) - => SaveAsJpegAsync(source, path, encoder, default); - /// /// Saves the image to the given stream with the Jpeg format. /// @@ -348,7 +275,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), @@ -363,15 +290,6 @@ namespace SixLabors.ImageSharp public static void SaveAsJpeg(this Image source, Stream stream) => SaveAsJpeg(source, stream, null); - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream) => SaveAsJpegAsync(source, stream, null); - /// /// Saves the image to the given stream with the Jpeg format. /// @@ -380,7 +298,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAsJpegAsync(source, stream, null, cancellationToken); /// @@ -396,17 +314,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder) - => SaveAsJpegAsync(source, stream, encoder, default); - /// /// Saves the image to the given stream with the Jpeg format. /// @@ -416,7 +323,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), @@ -462,17 +369,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder) - => SaveAsPngAsync(source, path, encoder, default); - /// /// Saves the image to the given stream with the Png format. /// @@ -482,7 +378,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), @@ -497,15 +393,6 @@ namespace SixLabors.ImageSharp public static void SaveAsPng(this Image source, Stream stream) => SaveAsPng(source, stream, null); - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream) => SaveAsPngAsync(source, stream, null); - /// /// Saves the image to the given stream with the Png format. /// @@ -514,7 +401,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAsPngAsync(source, stream, null, cancellationToken); /// @@ -530,17 +417,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder) - => SaveAsPngAsync(source, stream, encoder, default); - /// /// Saves the image to the given stream with the Png format. /// @@ -550,7 +426,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), @@ -596,17 +472,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder) - => SaveAsTgaAsync(source, path, encoder, default); - /// /// Saves the image to the given stream with the Tga format. /// @@ -616,7 +481,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), @@ -631,15 +496,6 @@ namespace SixLabors.ImageSharp public static void SaveAsTga(this Image source, Stream stream) => SaveAsTga(source, stream, null); - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream) => SaveAsTgaAsync(source, stream, null); - /// /// Saves the image to the given stream with the Tga format. /// @@ -648,7 +504,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAsTgaAsync(source, stream, null, cancellationToken); /// @@ -664,17 +520,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder) - => SaveAsTgaAsync(source, stream, encoder, default); - /// /// Saves the image to the given stream with the Tga format. /// @@ -684,7 +529,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken) => + public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.tt b/src/ImageSharp/Formats/ImageExtesions.Save.tt index 69ee4651a..63b404cc4 100644 --- a/src/ImageSharp/Formats/ImageExtesions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtesions.Save.tt @@ -79,17 +79,6 @@ namespace SixLabors.ImageSharp path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder) - => SaveAs<#= fmt #>Async(source, path, encoder, default); - /// /// Saves the image to the given stream with the <#= fmt #> format. /// @@ -99,7 +88,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the path is null. /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken) => + public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( path, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), @@ -114,15 +103,6 @@ namespace SixLabors.ImageSharp public static void SaveAs<#= fmt #>(this Image source, Stream stream) => SaveAs<#= fmt #>(source, stream, null); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream) => SaveAs<#= fmt #>Async(source, stream, null); - /// /// Saves the image to the given stream with the <#= fmt #> format. /// @@ -131,7 +111,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken) + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default) => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken); /// @@ -147,17 +127,6 @@ namespace SixLabors.ImageSharp stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder) - => SaveAs<#= fmt #>Async(source, stream, encoder, default); - /// /// Saves the image to the given stream with the <#= fmt #> format. /// @@ -167,7 +136,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// Thrown if the stream is null. /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken) => + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => source.SaveAsync( stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index ff4886a10..da23fb47d 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -160,7 +160,10 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// The pixel format. /// A representing the asynchronous operation. - private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) + private static async Task<(Image Image, IImageFormat Format)> DecodeAsync( + Stream stream, + Configuration config, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index eeb15e2ae..bf239c3e9 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -80,31 +80,6 @@ namespace SixLabors.ImageSharp } } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image file to open and to read the header from. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task IdentifyAsync(string filePath) - => IdentifyAsync(Configuration.Default, filePath, default); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The configuration. - /// The image file to open and to read the header from. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task IdentifyAsync(Configuration configuration, string filePath) - => IdentifyAsync(configuration, filePath, default); - /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -115,7 +90,7 @@ namespace SixLabors.ImageSharp /// The representing the asynchronous operation with the parameter type /// property set to null if suitable info detector is not found. /// - public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken) + public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken = default) => IdentifyAsync(Configuration.Default, filePath, cancellationToken); /// @@ -129,25 +104,16 @@ namespace SixLabors.ImageSharp /// The representing the asynchronous operation with the parameter type /// property set to null if suitable info detector is not found. /// - public static async Task IdentifyAsync(Configuration configuration, string filePath, CancellationToken cancellationToken) + public static async Task IdentifyAsync( + Configuration configuration, + string filePath, + CancellationToken cancellationToken = default) { (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken) .ConfigureAwait(false); return res.ImageInfo; } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image file to open and to read the header from. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(string filePath) - => IdentifyWithFormatAsync(Configuration.Default, filePath, default); - /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -160,24 +126,9 @@ namespace SixLabors.ImageSharp /// public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( string filePath, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) => IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The configuration. - /// The image file to open and to read the header from. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - Configuration configuration, - string filePath) - => IdentifyWithFormatAsync(configuration, filePath, default); - /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -192,7 +143,7 @@ namespace SixLabors.ImageSharp public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( Configuration configuration, string filePath, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { Guard.NotNull(configuration, nameof(configuration)); using Stream stream = configuration.FileSystem.OpenRead(filePath); @@ -211,17 +162,6 @@ namespace SixLabors.ImageSharp public static Image Load(string path) => Load(Configuration.Default, path); - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// A representing the asynchronous operation. - public static Task LoadAsync(string path) - => LoadAsync(Configuration.Default, path); - /// /// Create a new instance of the class from the given file. /// @@ -247,19 +187,6 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration configuration, string path) => Load(configuration, path, out _); - /// - /// Create a new instance of the class from the given file. - /// - /// The configuration for the decoder. - /// The file path to the image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(Configuration configuration, string path) - => LoadAsync(configuration, path, default(CancellationToken)); - /// /// Create a new instance of the class from the given file. /// @@ -271,7 +198,10 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static async Task LoadAsync(Configuration configuration, string path, CancellationToken cancellationToken) + public static async Task LoadAsync( + Configuration configuration, + string path, + CancellationToken cancellationToken = default) { using Stream stream = configuration.FileSystem.OpenRead(path); (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) @@ -313,7 +243,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(string path, CancellationToken cancellationToken) + public static Task LoadAsync(string path, CancellationToken cancellationToken = default) => LoadAsync(Configuration.Default, path, cancellationToken); /// @@ -330,21 +260,6 @@ namespace SixLabors.ImageSharp public static Task LoadAsync(string path, IImageDecoder decoder) => LoadAsync(Configuration.Default, path, decoder, default); - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder) - => LoadAsync(configuration, path, decoder, default); - /// /// Create a new instance of the class from the given file. /// @@ -361,23 +276,6 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => LoadAsync(Configuration.Default, path, decoder, default); - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Configuration configuration, string path, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => LoadAsync(configuration, path, decoder, default); - /// /// Create a new instance of the class from the given file. /// @@ -391,7 +289,11 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Configuration configuration, string path, IImageDecoder decoder, CancellationToken cancellationToken) + public static Task LoadAsync( + Configuration configuration, + string path, + IImageDecoder decoder, + CancellationToken cancellationToken = default) { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(path, nameof(path)); @@ -414,7 +316,11 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(Configuration configuration, string path, IImageDecoder decoder, CancellationToken cancellationToken) + public static Task> LoadAsync( + Configuration configuration, + string path, + IImageDecoder decoder, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); @@ -438,21 +344,6 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => LoadAsync(Configuration.Default, path, default(CancellationToken)); - /// - /// Create a new instance of the class from the given file. - /// - /// The configuration for the decoder. - /// The file path to the image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Configuration configuration, string path) - where TPixel : unmanaged, IPixel - => LoadAsync(configuration, path, default(CancellationToken)); - /// /// Create a new instance of the class from the given file. /// @@ -465,7 +356,10 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task> LoadAsync(Configuration configuration, string path, CancellationToken cancellationToken) + public static async Task> LoadAsync( + Configuration configuration, + string path, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { using Stream stream = configuration.FileSystem.OpenRead(path); diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index ac6eec282..ee148cd25 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -122,22 +122,6 @@ namespace SixLabors.ImageSharp public static IImageInfo Identify(Configuration configuration, Stream stream) => Identify(configuration, stream, out _); - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The configuration. - /// The image stream to read the information from. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// A representing the asynchronous operation or null if - /// a suitable detector is not found. - /// - public static Task IdentifyAsync(Configuration configuration, Stream stream) - => IdentifyAsync(configuration, stream, default); - /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -152,7 +136,10 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation or null if /// a suitable detector is not found. /// - public static async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static async Task IdentifyAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) { (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false); return res.ImageInfo; @@ -179,41 +166,6 @@ namespace SixLabors.ImageSharp return data.ImageInfo; } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the information from. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// A representing the asynchronous operation or null if - /// a suitable detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Stream stream) - => IdentifyWithFormatAsync(Configuration.Default, stream); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The configuration. - /// The image stream to read the information from. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The representing the asyncronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream) - => WithSeekableStreamAsync( - configuration, - stream, - (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), - default); - /// /// Reads the raw image information from the specified stream without fully decoding it. /// @@ -229,7 +181,7 @@ namespace SixLabors.ImageSharp /// public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( Stream stream, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken); /// @@ -246,7 +198,10 @@ namespace SixLabors.ImageSharp /// The representing the asynchronous operation with the parameter type /// property set to null if suitable info detector is not found. /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) => WithSeekableStreamAsync( configuration, stream, @@ -354,23 +309,6 @@ namespace SixLabors.ImageSharp return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); } - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The configuration for the decoder. - /// The stream containing image information. - /// The decoder. - /// The configuration is null. - /// The stream is null. - /// The decoder is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder) - => LoadAsync(configuration, stream, decoder, default); - /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. @@ -386,7 +324,11 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Configuration configuration, Stream stream, IImageDecoder decoder, CancellationToken cancellationToken) + public static Task LoadAsync( + Configuration configuration, + Stream stream, + IImageDecoder decoder, + CancellationToken cancellationToken = default) { Guard.NotNull(decoder, nameof(decoder)); return WithSeekableStreamAsync( @@ -409,23 +351,6 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); - /// - /// Decode a new instance of the class from the given stream. - /// - /// The configuration for the decoder. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task LoadAsync(Configuration configuration, Stream stream) - { - (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false); - return fmt.Image; - } - /// /// Decode a new instance of the class from the given stream. /// @@ -438,7 +363,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken) .ConfigureAwait(false); @@ -517,21 +442,6 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); - /// - /// Create a new instance of the class from the given stream. - /// - /// The stream containing image information. - /// The decoder. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => LoadAsync(stream, decoder, default); - /// /// Create a new instance of the class from the given stream. /// @@ -544,7 +454,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken) + public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( Configuration.Default, @@ -569,26 +479,6 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); - /// - /// Create a new instance of the class from the given stream. - /// - /// The Configuration. - /// The stream containing image information. - /// The decoder. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync( - Configuration configuration, - Stream stream, - IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => LoadAsync(configuration, stream, decoder, default); - /// /// Create a new instance of the class from the given stream. /// @@ -607,7 +497,7 @@ namespace SixLabors.ImageSharp Configuration configuration, Stream stream, IImageDecoder decoder, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel => WithSeekableStreamAsync( configuration, @@ -667,22 +557,6 @@ namespace SixLabors.ImageSharp throw new UnknownImageFormatException(sb.ToString()); } - /// - /// Create a new instance of the class from the given stream. - /// - /// The configuration options. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - Configuration configuration, - Stream stream) - => LoadWithFormatAsync(configuration, stream, default); - /// /// Create a new instance of the class from the given stream. /// @@ -695,7 +569,10 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) { (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, @@ -720,23 +597,6 @@ namespace SixLabors.ImageSharp throw new UnknownImageFormatException(sb.ToString()); } - /// - /// Create a new instance of the class from the given stream. - /// - /// The configuration options. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - => LoadWithFormatAsync(configuration, stream, default); - /// /// Create a new instance of the class from the given stream. /// @@ -750,7 +610,10 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { (Image Image, IImageFormat Format) data = @@ -777,22 +640,6 @@ namespace SixLabors.ImageSharp throw new UnknownImageFormatException(sb.ToString()); } - /// - /// Create a new instance of the class from the given stream. - /// - /// The configuration options. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - => LoadAsync(configuration, stream, default(CancellationToken)); - /// /// Create a new instance of the class from the given stream. /// @@ -806,7 +653,10 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task> LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public static async Task> LoadAsync( + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 3560d9960..fbb3ec206 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -99,16 +99,6 @@ namespace SixLabors.ImageSharp this.AcceptVisitor(new EncodeVisitor(encoder, stream)); } - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream or encoder is null. - /// A representing the asynchronous operation. - public Task SaveAsync(Stream stream, IImageEncoder encoder) - => this.SaveAsync(stream, encoder, default); - /// /// Saves the image to the given stream using the given image encoder. /// @@ -117,7 +107,7 @@ namespace SixLabors.ImageSharp /// 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, IImageEncoder encoder, CancellationToken cancellationToken) + public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index 5430810b2..ccccf526f 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -26,16 +26,6 @@ namespace SixLabors.ImageSharp public static void Save(this Image source, string path) => source.Save(path, source.DetectEncoder(path)); - /// - /// Writes the image to the given stream using the currently loaded image format. - /// - /// The source image. - /// The file path to save the image to. - /// The path is null. - /// A representing the asynchronous operation. - public static Task SaveAsync(this Image source, string path) - => source.SaveAsync(path, source.DetectEncoder(path)); - /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -44,7 +34,7 @@ namespace SixLabors.ImageSharp /// The token to monitor for cancellation requests. /// The path is null. /// A representing the asynchronous operation. - public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken) + public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default) => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); /// From 4a05ac558504fbd223e45a54341d7f3938e5604a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Aug 2020 01:55:33 +0200 Subject: [PATCH 30/33] address review findings --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 13 ++++--------- src/ImageSharp/Formats/Gif/GifDecoder.cs | 6 ++---- src/ImageSharp/Formats/ImageDecoderUtilities.cs | 8 ++------ ...ageExtesions.Save.cs => ImageExtensions.Save.cs} | 0 ...ageExtesions.Save.tt => ImageExtensions.Save.tt} | 0 src/ImageSharp/ImageSharp.csproj | 9 ++++----- 6 files changed, 12 insertions(+), 24 deletions(-) rename src/ImageSharp/Formats/{ImageExtesions.Save.cs => ImageExtensions.Save.cs} (100%) rename src/ImageSharp/Formats/{ImageExtesions.Save.tt => ImageExtensions.Save.tt} (100%) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index fe831caf8..24d187830 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, CreateLargeImageException); + return decoder.Decode(configuration, stream); } /// @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, CreateLargeImageException, cancellationToken); + return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(configuration, stream, CreateLargeImageException); + return new BmpDecoderCore(configuration, this).Identify(configuration, stream); } /// @@ -72,12 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, CreateLargeImageException, cancellationToken); - } - - private static InvalidImageContentException CreateLargeImageException(InvalidMemoryOperationException ex, Size dims) - { - return new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); + return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index e42f9c9f2..f665cc0ab 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -63,14 +63,12 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - - using var bufferedStream = new BufferedReadStream(configuration, stream); - return await decoder.IdentifyAsync(configuration, bufferedStream, cancellationToken); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 5e4aeb9eb..a5f5819bd 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -127,14 +127,10 @@ namespace SixLabors.ImageSharp.Formats } } - public static IImageInfo Identify(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) - => decoder.Identify(configuration, stream, DefaultLargeImageExceptionFactory); - public static IImageInfo Identify( this IImageDecoderInternals decoder, Configuration configuration, - Stream stream, - Func largeImageExceptionFactory) + Stream stream) { using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -144,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats } catch (InvalidMemoryOperationException ex) { - throw largeImageExceptionFactory(ex, decoder.Dimensions); + throw new InvalidImageContentException(decoder.Dimensions, ex); } } diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs similarity index 100% rename from src/ImageSharp/Formats/ImageExtesions.Save.cs rename to src/ImageSharp/Formats/ImageExtensions.Save.cs diff --git a/src/ImageSharp/Formats/ImageExtesions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt similarity index 100% rename from src/ImageSharp/Formats/ImageExtesions.Save.tt rename to src/ImageSharp/Formats/ImageExtensions.Save.tt diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 8b2bbd701..c3d9618c8 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -17,7 +17,6 @@ SixLabors.ImageSharp Image Resize Crop Gif Jpg Jpeg Bitmap Png Core SixLabors.ImageSharp - SA1636 @@ -133,10 +132,10 @@ True PorterDuffFunctions.Generated.tt - + True True - ImageExtesions.Save.tt + ImageExtensions.Save.tt @@ -213,9 +212,9 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator - + TextTemplatingFileGenerator - ImageExtesions.Save.cs + ImageExtensions.Save.cs From b5650cdef41f6a01cc4c71a7cff94b092556dc23 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Aug 2020 02:00:21 +0200 Subject: [PATCH 31/33] add ConfigureAwait --- src/ImageSharp/Formats/ImageEncoderUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs index b8365368b..896fffa6f 100644 --- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats Configuration configuration = image.GetConfiguration(); if (stream.CanSeek) { - await DoEncodeAsync(stream); + await DoEncodeAsync(stream).ConfigureAwait(false); } else { From 83ab00d48d074c36e3d66e163f9187c42cc633e9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Aug 2020 02:07:26 +0200 Subject: [PATCH 32/33] one leftover --- src/ImageSharp/ImageExtensions.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index ccccf526f..d40c5c271 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -55,18 +55,6 @@ namespace SixLabors.ImageSharp } } - /// - /// Writes the image to the given stream using the currently loaded image format. - /// - /// The source image. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The path is null. - /// The encoder is null. - /// A representing the asynchronous operation. - public static Task SaveAsync(this Image source, string path, IImageEncoder encoder) - => SaveAsync(source, path, encoder, default); - /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -81,7 +69,7 @@ namespace SixLabors.ImageSharp this Image source, string path, IImageEncoder encoder, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { Guard.NotNull(path, nameof(path)); Guard.NotNull(encoder, nameof(encoder)); From ae1dbabfd030a85fba085acb52e4d64c40c85d91 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 4 Aug 2020 11:54:43 +0100 Subject: [PATCH 33/33] Minor fixes. --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 2 +- src/ImageSharp/Formats/ImageDecoderUtilities.cs | 8 ++++---- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 15 +++++++++++---- src/ImageSharp/Formats/Png/PngDecoder.cs | 5 ++--- src/ImageSharp/Formats/Png/PngEncoder.cs | 3 +++ src/ImageSharp/Formats/Tga/TgaDecoder.cs | 3 ++- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 24d187830..129b3a1aa 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index f665cc0ab..196d77ad7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index a5f5819bd..5d77fb0c8 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats { try { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); + using var bufferedReadStream = new BufferedReadStream(configuration, stream); IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); return Task.FromResult(imageInfo); } @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats { try { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); + using var bufferedReadStream = new BufferedReadStream(configuration, stream); Image image = decoder.Decode(bufferedReadStream, cancellationToken); return Task.FromResult(image); } @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats Configuration configuration, Stream stream) { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); + using var bufferedReadStream = new BufferedReadStream(configuration, stream); try { @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats Func largeImageExceptionFactory) where TPixel : unmanaged, IPixel { - using BufferedReadStream bufferedReadStream = new BufferedReadStream(configuration, stream); + using var bufferedReadStream = new BufferedReadStream(configuration, stream); try { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 93697db95..39b8e492f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -44,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -56,12 +57,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var decoder = new JpegDecoderCore(configuration, this); - return decoder.IdentifyAsync(configuration, stream, 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 (var decoder = new JpegDecoderCore(configuration, this)) + { + return await decoder.IdentifyAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + } } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 1ee47a017..479c24b7e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -4,8 +4,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -39,7 +37,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 7e563596e..e72e8d3d5 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -77,6 +77,9 @@ namespace SixLabors.ImageSharp.Formats.Png public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + // 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 (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index bad769fa9..e06a0ee88 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -41,7 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Tga /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream)