diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index 5791c6e92..c5abbda61 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 = default)
+ => 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/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 cb26ff606..129b3a1aa 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(configuration, 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(configuration, stream);
}
///
@@ -55,46 +45,34 @@ 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(configuration, 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(configuration, 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)
{
Guard.NotNull(stream, nameof(stream));
- using var bufferedStream = new BufferedReadStream(configuration, stream);
- return new BmpDecoderCore(configuration, this).Identify(bufferedStream);
+ return new BmpDecoderCore(configuration, this).Identify(configuration, 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(configuration, stream);
- return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
+ return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken);
}
}
}
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/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/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 2b7103072..196d77ad7 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(configuration, 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(configuration, 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(configuration, 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(configuration, 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,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
var decoder = new GifDecoderCore(configuration, this);
using var bufferedStream = new BufferedReadStream(configuration, stream);
- return decoder.Identify(bufferedStream);
+ return decoder.Identify(bufferedStream, default);
}
///
- public Task IdentifyAsync(Configuration configuration, Stream stream)
+ 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 decoder.IdentifyAsync(bufferedStream);
+ return decoder.IdentifyAsync(configuration, stream, 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/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/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/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/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/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..5d77fb0c8 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,159 @@ namespace SixLabors.ImageSharp.Formats
/// Reads the raw image information from the specified stream.
///
/// The decoder.
- /// The containing image data.
+ /// /// 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, BufferedReadStream stream)
- => Task.FromResult(decoder.Identify(stream));
+ public static Task IdentifyAsync(
+ this IImageDecoderInternals decoder,
+ Configuration configuration,
+ Stream stream,
+ CancellationToken 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.
+ /// is null.
+ /// A representing the asynchronous operation.
+ public static Task IdentifyAsync(
+ this IImageDecoderInternals decoder,
+ Configuration configuration,
+ Stream stream,
+ Func tooLargeImageExceptionFactory,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ using var bufferedReadStream = new BufferedReadStream(configuration, 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 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);
///
/// Decodes the image from the specified stream.
///
/// The pixel format.
/// The decoder.
- /// The containing image data.
+ /// 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, BufferedReadStream stream)
+ public static Task> DecodeAsync(
+ this IImageDecoderInternals decoder,
+ Configuration configuration,
+ Stream stream,
+ Func largeImageExceptionFactory,
+ CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
- => Task.FromResult(decoder.Decode(stream));
+ {
+ try
+ {
+ using var bufferedReadStream = new BufferedReadStream(configuration, 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,
+ Configuration configuration,
+ Stream stream)
+ {
+ using var bufferedReadStream = new BufferedReadStream(configuration, stream);
+
+ try
+ {
+ return decoder.Identify(bufferedReadStream, default);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ throw new InvalidImageContentException(decoder.Dimensions, ex);
+ }
+ }
+
+ public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream)
+ where TPixel : unmanaged, IPixel
+ => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory);
+
+ public static Image Decode(
+ this IImageDecoderInternals decoder,
+ Configuration configuration,
+ Stream stream,
+ Func largeImageExceptionFactory)
+ where TPixel : unmanaged, IPixel
+ {
+ using var bufferedReadStream = new BufferedReadStream(configuration, 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/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs
new file mode 100644
index 000000000..896fffa6f
--- /dev/null
+++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs
@@ -0,0 +1,61 @@
+// 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.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ internal static class ImageEncoderUtilities
+ {
+ public static async Task EncodeAsync(
+ this IImageEncoderInternals encoder,
+ Image image,
+ Stream stream,
+ CancellationToken cancellationToken)
+ where TPixel : unmanaged, IPixel
+ {
+ Configuration configuration = image.GetConfiguration();
+ if (stream.CanSeek)
+ {
+ await DoEncodeAsync(stream).ConfigureAwait(false);
+ }
+ else
+ {
+ using var ms = new MemoryStream();
+ await DoEncodeAsync(ms);
+ ms.Position = 0;
+ await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ Task DoEncodeAsync(Stream innerStream)
+ {
+ try
+ {
+ encoder.Encode(image, innerStream, cancellationToken);
+ return Task.CompletedTask;
+ }
+ catch (OperationCanceledException)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+ }
+
+ public static void Encode(
+ this IImageEncoderInternals encoder,
+ Image image,
+ Stream stream)
+ where TPixel : unmanaged, IPixel
+ => encoder.Encode(image, stream, default);
+ }
+}
diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs
new file mode 100644
index 000000000..075c708b6
--- /dev/null
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs
@@ -0,0 +1,539 @@
+// 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.
+ /// 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 = default) =>
+ 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.
+ /// 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 = default)
+ => 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.
+ /// 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 = default) =>
+ 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.
+ /// 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 = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.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.
+ /// 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 = default)
+ => 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.
+ /// 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 = default) =>
+ 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.
+ /// 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 = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.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.
+ /// 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 = default)
+ => 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.
+ /// 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 = default) =>
+ 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.
+ /// 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 = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.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.
+ /// 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 = default)
+ => 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.
+ /// 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 = default) =>
+ 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.
+ /// 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 = default) =>
+ source.SaveAsync(
+ path,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.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.
+ /// 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 = default)
+ => 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.
+ /// 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 = default) =>
+ source.SaveAsync(
+ stream,
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
+ cancellationToken);
+
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/ImageExtensions.Save.tt
similarity index 51%
rename from src/ImageSharp/Formats/Bmp/ImageExtensions.cs
rename to src/ImageSharp/Formats/ImageExtensions.Save.tt
index 8d97c8b46..63b404cc4 100644
--- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs
+++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt
@@ -1,10 +1,32 @@
+<#@ 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;
-using SixLabors.ImageSharp.Formats.Bmp;
+
+<#
+ var formats = new []{
+ "Bmp",
+ "Gif",
+ "Jpeg",
+ "Png",
+ "Tga",
+ };
+
+ foreach (string fmt in formats)
+ {
+#>
+using SixLabors.ImageSharp.Formats.<#= fmt #>;
+<#
+
+ }
+#>
namespace SixLabors.ImageSharp
{
@@ -13,88 +35,115 @@ namespace SixLabors.ImageSharp
///
public static partial class ImageExtensions
{
+<#
+ foreach (string fmt in formats)
+ {
+#>
///
- /// Saves the image to the given stream with the bmp format.
+ /// 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 SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, 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 bmp format.
+ /// 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 SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null);
+ 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 bmp format.
+ /// 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 SaveAsBmp(this Image source, string path, BmpEncoder encoder) =>
+ public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) =>
source.Save(
path,
- encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// Saves the image to the given stream with the bmp format.
+ /// 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 SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder) =>
+ 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(BmpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
+ cancellationToken);
///
- /// Saves the image to the given stream with the bmp format.
+ /// 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 SaveAsBmp(this Image source, Stream stream) => SaveAsBmp(source, stream, 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 bmp format.
+ /// 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 SaveAsBmpAsync(this Image source, Stream stream) => SaveAsBmpAsync(source, stream, null);
+ public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default)
+ => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
///
- /// Saves the image to the given stream with the bmp format.
+ /// 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.
- public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) =>
- source.Save(
+ /// 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(BmpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
///
- /// Saves the image to the given stream with the bmp format.
+ /// 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 SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder) =>
+ 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(BmpFormat.Instance));
+ encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance),
+ cancellationToken);
+
+<#
+ }
+#>
}
}
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/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/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 3eaf3a4c4..39b8e492f 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(configuration, 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(configuration, stream);
}
///
@@ -45,31 +33,19 @@ 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(configuration, 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(configuration, 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 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Guard.NotNull(stream, nameof(stream));
using var decoder = new JpegDecoderCore(configuration, this);
- using var bufferedStream = new BufferedReadStream(configuration, stream);
-
- return decoder.Identify(bufferedStream);
+ return decoder.Identify(configuration, stream);
}
///
- public Task IdentifyAsync(Configuration configuration, Stream stream)
+ public async 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(configuration, stream);
-
- return decoder.IdentifyAsync(bufferedStream);
+ // 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/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index c0b09c4c2..c4355cdbe 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,21 +209,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
///
- public Image Decode(BufferedReadStream stream)
+ 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)
+ public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
- this.ParseStream(stream, true);
+ this.ParseStream(stream, true, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
@@ -233,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();
@@ -263,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
@@ -279,7 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
- this.ProcessStartOfScanMarker(stream);
+ this.ProcessStartOfScanMarker(stream, cancellationToken);
break;
}
else
@@ -996,8 +1003,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)
{
@@ -1048,7 +1054,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
spectralStart,
spectralEnd,
successiveApproximation >> 4,
- successiveApproximation & 15);
+ successiveApproximation & 15,
+ cancellationToken);
sd.ParseEntropyCodedData();
}
@@ -1081,7 +1088,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)
@@ -1097,7 +1104,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/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..593fe9277 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,11 +195,13 @@ 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));
+ cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max)
@@ -247,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;
@@ -396,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.)
@@ -418,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)
@@ -943,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.)
@@ -953,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;
}
@@ -970,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.)
@@ -998,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/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/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index 87e0195c3..479c24b7e 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -2,9 +2,8 @@
// 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;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@@ -22,65 +21,37 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel
{
var decoder = new PngDecoderCore(configuration, this);
-
- try
- {
- using var bufferedStream = new BufferedReadStream(configuration, 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(configuration, 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(configuration, 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(configuration, 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)
{
var decoder = new PngDecoderCore(configuration, this);
- using var bufferedStream = new BufferedReadStream(configuration, stream);
- return decoder.Identify(bufferedStream);
+ return decoder.Identify(configuration, 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(configuration, stream);
- return decoder.IdentifyAsync(bufferedStream);
+ return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 89caac3f6..3fa0e3f58 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -9,6 +9,8 @@ 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;
using SixLabors.ImageSharp.Formats.Png.Zlib;
@@ -131,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();
@@ -223,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/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index 61ea7c468..e72e8d3d5 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,17 @@ 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
{
+ // 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).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/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/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
index 25aa233db..e06a0ee88 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(configuration, 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(configuration, stream);
}
///
@@ -43,49 +30,34 @@ 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(configuration, 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(configuration, 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)
{
Guard.NotNull(stream, nameof(stream));
- using var bufferedStream = new BufferedReadStream(configuration, stream);
- return new TgaDecoderCore(configuration, this).Identify(bufferedStream);
+ return new TgaDecoderCore(configuration, this).Identify(configuration, 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(configuration, stream);
- return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream);
+ return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, 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/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.Decode.cs b/src/ImageSharp/Image.Decode.cs
index 683590fd1..da23fb47d 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,18 +157,24 @@ 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);
+ (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);
}
@@ -183,7 +190,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)
@@ -191,7 +198,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);
}
@@ -221,11 +228,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);
@@ -239,7 +247,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.FromFile.cs b/src/ImageSharp/Image.FromFile.cs
index a078f2db9..bf239c3e9 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;
@@ -80,15 +81,75 @@ namespace SixLabors.ImageSharp
}
///
- /// Create a new instance of the class from the given file.
+ /// Reads the raw image information from the specified stream without fully decoding it.
///
- /// The file path to the image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
- /// The .
- public static Image Load(string path)
- => Load(Configuration.Default, path);
+ /// 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 = default)
+ => 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 = 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 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 = 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 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 = default)
+ {
+ 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.
@@ -97,9 +158,9 @@ namespace SixLabors.ImageSharp
///
/// Thrown if the stream is not readable nor seekable.
///
- /// A representing the asynchronous operation.
- public static Task LoadAsync(string path)
- => LoadAsync(Configuration.Default, path);
+ /// The .
+ public static Image Load(string path)
+ => Load(Configuration.Default, path);
///
/// Create a new instance of the class from the given file.
@@ -131,18 +192,21 @@ namespace SixLabors.ImageSharp
///
/// 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)
+ 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).ConfigureAwait(false);
- return img;
- }
+ using Stream stream = configuration.FileSystem.OpenRead(path);
+ (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken)
+ .ConfigureAwait(false);
+ return img;
}
///
@@ -168,27 +232,140 @@ 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 = default)
+ => LoadAsync(Configuration.Default, path, cancellationToken);
+
+ ///
+ /// 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.
+ ///
+ /// 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.
+ ///
+ /// 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 = default)
+ {
+ 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.
/// 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)
+ public static Task> LoadAsync(
+ Configuration configuration,
+ string path,
+ IImageDecoder decoder,
+ CancellationToken cancellationToken = default)
+ 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);
- }
+ 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 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.
+ ///
+ /// 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 = default)
+ where TPixel : unmanaged, IPixel
+ {
+ using Stream stream = configuration.FileSystem.OpenRead(path);
+ (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken)
+ .ConfigureAwait(false);
+ return img;
}
///
diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs
index fae88f21e..ee148cd25 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.
@@ -125,6 +127,7 @@ namespace SixLabors.ImageSharp
///
/// 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.
@@ -133,9 +136,12 @@ 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 async Task IdentifyAsync(
+ Configuration configuration,
+ Stream stream,
+ CancellationToken cancellationToken = default)
{
- (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;
}
@@ -164,35 +170,43 @@ namespace SixLabors.ImageSharp
/// 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.
///
- /// A representing the asynchronous operation or null if
- /// a suitable detector is not found.
+ /// 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)
- => IdentifyWithFormatAsync(Configuration.Default, stream);
+ public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
+ Stream stream,
+ CancellationToken cancellationToken = default)
+ => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken);
///
/// 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
+ /// 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)
+ public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
+ Configuration configuration,
+ Stream stream,
+ CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync(
configuration,
stream,
- s => InternalIdentityAsync(s, configuration ?? Configuration.Default));
+ (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct),
+ cancellationToken);
///
/// Decode a new instance of the class from the given stream.
@@ -302,6 +316,7 @@ namespace SixLabors.ImageSharp
/// 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.
@@ -309,13 +324,18 @@ 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)
+ public static Task LoadAsync(
+ Configuration configuration,
+ Stream stream,
+ IImageDecoder decoder,
+ CancellationToken cancellationToken = default)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStreamAsync(
configuration,
stream,
- s => decoder.DecodeAsync(configuration, s));
+ (s, ct) => decoder.DecodeAsync(configuration, s, ct),
+ cancellationToken);
}
///
@@ -336,15 +356,17 @@ namespace SixLabors.ImageSharp
///
/// 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)
+ public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default)
{
- (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream).ConfigureAwait(false);
+ (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken)
+ .ConfigureAwait(false);
return fmt.Image;
}
@@ -425,18 +447,20 @@ namespace SixLabors.ImageSharp
///
/// 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)
+ public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel
=> WithSeekableStreamAsync(
Configuration.Default,
stream,
- s => decoder.DecodeAsync(Configuration.Default, s));
+ (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct),
+ cancellationToken);
///
/// Create a new instance of the class from the given stream.
@@ -461,6 +485,7 @@ namespace SixLabors.ImageSharp
/// 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.
@@ -468,12 +493,17 @@ 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,
+ CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel
=> WithSeekableStreamAsync(
configuration,
stream,
- s => decoder.DecodeAsync(configuration, s));
+ (s, ct) => decoder.DecodeAsync(configuration, s, ct),
+ cancellationToken);
///
/// Create a new instance of the class from the given stream.
@@ -532,18 +562,23 @@ namespace SixLabors.ImageSharp
///
/// 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)
+ public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(
+ Configuration configuration,
+ Stream stream,
+ CancellationToken cancellationToken = default)
{
(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)
@@ -567,6 +602,7 @@ namespace SixLabors.ImageSharp
///
/// 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.
@@ -574,14 +610,18 @@ 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 async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(
+ Configuration configuration,
+ Stream stream,
+ CancellationToken cancellationToken = default)
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)
@@ -605,6 +645,7 @@ namespace SixLabors.ImageSharp
///
/// 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.
@@ -612,10 +653,14 @@ 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 async Task> LoadAsync(
+ Configuration configuration,
+ Stream stream,
+ CancellationToken cancellationToken = default)
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 +745,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 +772,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, configuration.StreamProcessingBufferSize).ConfigureAwait(false);
+ await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
- return await action(memoryStream).ConfigureAwait(false);
+ return await action(memoryStream, cancellationToken).ConfigureAwait(false);
}
}
}
diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs
index 605f5e0da..fbb3ec206 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;
@@ -103,15 +104,16 @@ namespace SixLabors.ImageSharp
///
/// 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)
+ public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default)
{
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 +164,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 +182,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/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs
index b227679a6..d40c5c271 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;
@@ -30,10 +31,11 @@ namespace SixLabors.ImageSharp
///
/// 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)
- => source.SaveAsync(path, source.DetectEncoder(path));
+ public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default)
+ => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken);
///
/// Writes the image to the given stream using the currently loaded image format.
@@ -59,17 +61,20 @@ namespace SixLabors.ImageSharp
/// 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)
+ public static async Task SaveAsync(
+ this Image source,
+ string path,
+ IImageEncoder encoder,
+ CancellationToken cancellationToken = default)
{
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 64a496141..c3d9618c8 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -20,10 +20,10 @@
-
+
-
+
@@ -41,7 +41,7 @@
-
+
True
True
@@ -132,6 +132,11 @@
True
PorterDuffFunctions.Generated.tt
+
+ True
+ True
+ ImageExtensions.Save.tt
+
@@ -207,6 +212,10 @@
DefaultPixelBlenders.Generated.cs
TextTemplatingFileGenerator
+
+ TextTemplatingFileGenerator
+ ImageExtensions.Save.cs
+
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/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..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.
+// 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,
- EdgeDetectionOperators 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,
- EdgeDetectionOperators 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,
- EdgeDetectionOperators 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(EdgeDetectionOperators filter, bool grayscale)
- {
- IImageProcessor processor;
-
- switch (filter)
- {
- case EdgeDetectionOperators.Kayyali:
- processor = new KayyaliProcessor(grayscale);
- break;
-
- case EdgeDetectionOperators.Kirsch:
- processor = new KirschProcessor(grayscale);
- break;
-
- case EdgeDetectionOperators.Laplacian3x3:
- processor = new Laplacian3x3Processor(grayscale);
- break;
-
- case EdgeDetectionOperators.Laplacian5x5:
- processor = new Laplacian5x5Processor(grayscale);
- break;
-
- case EdgeDetectionOperators.LaplacianOfGaussian:
- processor = new LaplacianOfGaussianProcessor(grayscale);
- break;
-
- case EdgeDetectionOperators.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 EdgeDetectionOperators.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 EdgeDetectionOperators.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 EdgeDetectionOperators.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;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs
new file mode 100644
index 000000000..2e279d340
--- /dev/null
+++ b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.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 KnownEdgeDetectorKernels
+ {
+ ///
+ /// 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/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 164488155..27963613e 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
@@ -18,25 +18,27 @@ 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.
- /// 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, CompassKernels 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;
+ this.grayscale = definition.Grayscale;
+ this.kernels = definition.Kernel.Flatten();
}
- private CompassKernels Kernels { get; }
-
- private bool Grayscale { get; }
-
///
protected override void BeforeImageApply()
{
@@ -45,7 +47,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 +58,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.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 45639d93a..62dd54919 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
@@ -13,33 +13,27 @@ 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.
///
/// 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 DenseMatrix kernelXY,
- bool grayscale,
+ EdgeDetectorProcessor definition,
Image source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
- this.KernelXY = kernelXY;
- this.Grayscale = grayscale;
+ this.kernelXY = definition.Kernel.KernelXY;
+ this.grayscale = definition.Grayscale;
}
- public bool Grayscale { get; }
-
- ///
- /// Gets the 2d gradient operator.
- ///
- public DenseMatrix KernelXY { get; }
-
///
protected override void BeforeImageApply()
{
@@ -48,7 +42,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 +53,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/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/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..bda861799
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs
@@ -0,0 +1,163 @@
+// 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