diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index 5791c6e92b..c5abbda61b 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -5,6 +5,7 @@
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 @@ public static void AcceptVisitor(this Image source, IImageVisitor visitor)
///
/// 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 fbfdafeb10..ccff180266 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 @@ public interface IImageVisitorAsync
/// 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 3b86415324..e4713e237c 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 @@ public InvalidImageContentException(string errorMessage, Exception innerExceptio
: 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 cb26ff606a..129b3a1aa0 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 @@ public Image Decode(Configuration configuration, Stream stream)
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 @@ 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
{
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 ea8fd11a86..6f92236372 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -6,6 +6,7 @@
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 @@ public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
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 @@ public Image Decode(BufferedReadStream stream)
}
///
- 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 08c9bde00d..2f5c4b7cf7 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 @@ public void Encode(Image image, Stream stream)
}
///
- 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 b3f64eea6a..eb29c44050 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -5,6 +5,7 @@
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 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
/// 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 2b7103072b..196d77ad77 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 @@ public Image Decode(Configuration configuration, Stream stream)
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 @@ 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 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 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
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 78ffee8bdb..8f5cc3b5c8 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -6,6 +6,7 @@
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 @@ public GifDecoderCore(Configuration configuration, IGifDecoderOptions options)
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 @@ public Image Decode(BufferedReadStream stream)
}
///
- 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 539ab0fb38..116ee3daeb 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 @@ public void Encode(Image image, Stream stream)
}
///
- 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 556ace203a..070864e603 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -6,6 +6,7 @@
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 @@ public GifEncoderCore(Configuration configuration, IGifEncoderOptions options)
/// 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 d262b056cd..0000000000
--- 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 97886e5269..b55f1119b3 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 @@ Image Decode(Configuration configuration, Stream stream)
/// 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 @@ Task> DecodeAsync(Configuration configuration, Stream stre
///
/// 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 33748bf245..e190f7adda 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 @@ internal interface IImageDecoderInternals
///
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 646e0ecc09..e5a1b1c839 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 @@ void Encode(Image image, Stream stream)
/// 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 0000000000..d44ac45f27
--- /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 862c64999b..6f5fc23338 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 @@ public interface IImageInfoDetector
///
/// 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 9d1639a090..5d77fb0c8c 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 @@ internal static class ImageDecoderUtilities
/// 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 0000000000..896fffa6fc
--- /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 0000000000..075c708b6a
--- /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 8d97c8b464..63b404cc44 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 d6c16f8260..6424ee23ac 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 @@ internal class HuffmanScanDecoder
private HuffmanScanBuffer scanBuffer;
+ private CancellationToken cancellationToken;
+
///
/// Initializes a new instance of the class.
///
@@ -63,6 +66,7 @@ internal class HuffmanScanDecoder
/// 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 @@ public HuffmanScanDecoder(
int spectralStart,
int spectralEnd,
int successiveHigh,
- int successiveLow)
+ int successiveLow,
+ CancellationToken cancellationToken)
{
this.dctZigZag = ZigZag.CreateUnzigTable();
this.stream = stream;
@@ -89,6 +94,7 @@ public HuffmanScanDecoder(
this.spectralEnd = spectralEnd;
this.successiveHigh = successiveHigh;
this.successiveLow = successiveLow;
+ this.cancellationToken = cancellationToken;
}
///
@@ -96,6 +102,8 @@ public HuffmanScanDecoder(
///
public void ParseEntropyCodedData()
{
+ this.cancellationToken.ThrowIfCancellationRequested();
+
if (!this.frame.Progressive)
{
this.ParseBaselineData();
@@ -145,6 +153,8 @@ private void ParseBaselineDataInterleaved()
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 @@ private void ParseBaselineDataNonInterleaved()
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 @@ private void ParseProgressiveDataNonInterleaved()
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 @@ ref Unsafe.Add(ref blockRef, i),
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 716bb9eb02..5b0331c85c 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 @@ public void Dispose()
///
/// 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 @@ public void PostProcess(ImageFrame destination)
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 d6600b6253..0000000000
--- 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 3eaf3a4c47..39b8e492f8 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 @@ public Image Decode(Configuration configuration, Stream stream)
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 @@ 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
{
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 @@ public IImageInfo Identify(Configuration configuration, Stream stream)
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 c0b09c4c2b..c4355cdbe1 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -5,6 +5,7 @@
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 @@ public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
///
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 @@ public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStrea
}
///
- 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 @@ public IImageInfo Identify(BufferedReadStream stream)
///
/// 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 @@ public void ParseStream(BufferedReadStream stream, bool metadataOnly = false)
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 @@ public void ParseStream(BufferedReadStream stream, bool metadataOnly = false)
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
- this.ProcessStartOfScanMarker(stream);
+ this.ProcessStartOfScanMarker(stream, cancellationToken);
break;
}
else
@@ -996,8 +1003,7 @@ private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int r
///
/// 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 @@ private void ProcessStartOfScanMarker(BufferedReadStream stream)
spectralStart,
spectralEnd,
successiveApproximation >> 4,
- successiveApproximation & 15);
+ successiveApproximation & 15,
+ cancellationToken);
sd.ParseEntropyCodedData();
}
@@ -1081,7 +1088,7 @@ private ushort ReadUint16(BufferedReadStream stream)
///
/// 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 @@ private Image PostProcessIntoImage()
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 08d793401e..b549bd8a32 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 @@ public void Encode(Image image, Stream stream)
/// 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 f755028dbd..593fe92776 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -6,6 +6,7 @@
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 @@ public JpegEncoderCore(IJpegEncoderOptions options)
/// 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 @@ public void Encode(Image image, Stream stream)
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 @@ private void EmitHuffRLE(HuffIndex index, int runLength, int value)
///
/// 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 @@ private void Encode444(Image pixels)
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 @@ private void WriteStartOfFrame(int width, int height, int componentCount)
///
/// 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 @@ private void WriteStartOfScan(Image image)
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 @@ private void WriteStartOfScan(Image image)
///
/// 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 @@ private void Encode420(Image pixels)
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 e6a5265b27..0000000000
--- 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 87e0195c35..479c24b7e8 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 @@ public Image Decode(Configuration configuration, Stream stream)
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 89caac3f68..3fa0e3f586 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -9,6 +9,8 @@
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 @@ public PngDecoderCore(Configuration configuration, IPngDecoderOptions options)
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 @@ public Image Decode(BufferedReadStream stream)
}
///
- 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 61ea7c4684..e72e8d3d55 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 @@ public void Encode(Image image, Stream stream)
/// 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 a39fdd91f7..5cf11099cd 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -7,6 +7,7 @@
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 @@ public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configurati
/// 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 f39738eae3..0000000000
--- 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 25aa233db8..e06a0ee887 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 @@ public Image Decode(Configuration configuration, Stream stream)
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 @@ 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
{
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 7cd83fedbf..eef6e7362b 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 @@ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
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 @@ private void ReadRle(int width, int height, Buffer2D pixels, int
}
///
- 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 c8f8fb1a51..529d951cd3 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 @@ public void Encode(Image image, Stream stream)
}
///
- 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 d9e7c0e6bc..d3a628531e 100644
--- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -5,6 +5,7 @@
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 @@ public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocato
/// 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 683590fd1a..da23fb47dd 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 @@ private static (Image Image, IImageFormat Format) Decode(Stream
///
/// 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 @@ private static (Image Image, IImageFormat Format) Decode(Stream stream, Configur
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 @@ private static (Image Image, IImageFormat Format) Decode(Stream stream, Configur
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 @@ private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stre
///
/// 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 @@ private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stre
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 a078f2db98..bf239c3e9f 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 @@ public static IImageInfo Identify(Configuration configuration, string filePath,
}
///
- /// 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 @@ public static Image Load(string path)
///
/// 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 @@ public static Image Load(Configuration configuration, string path)
///
/// 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 @@ public static Image Load(Configuration configuration, string path, IImageDecoder
}
}
+ ///
+ /// 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 fae88f21e1..ee148cd254 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -5,6 +5,7 @@
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 @@ public static Task DetectFormatAsync(Configuration configuration,
=> 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 @@ public static IImageInfo Identify(Configuration configuration, Stream stream)
///
/// 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 @@ public static IImageInfo Identify(Configuration configuration, Stream stream)
/// 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 @@ public static IImageInfo Identify(Configuration configuration, Stream stream, ou
/// 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 @@ public static Image Load(Configuration configuration, Stream stream, IImageDecod
/// 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 @@ public static Image Load(Configuration configuration, Stream stream, IImageDecod
/// 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 @@ public static Task LoadAsync(Configuration configuration, Stream stream,
///
/// The configuration for the decoder.
/// The stream containing image information.
+ /// The token to monitor for cancellation requests.
/// The configuration is null.
/// The stream is null.
/// The stream is not readable.
/// Image format not recognised.
/// Image contains invalid content.
/// A representing the asynchronous operation.
- public static async Task LoadAsync(Configuration configuration, Stream stream)
+ 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 @@ public static Image Load(Stream stream, IImageDecoder decoder)
///
/// 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 @@ public static Image Load(Configuration configuration, Stream str
/// 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 @@ public static Image Load(Configuration configuration, Stream str
/// 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 @@ public static Image Load(Configuration configuration, Stream str
///
/// 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 @@ public static Image Load(Configuration configuration, Stream str
///
/// 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 @@ public static Image Load(Configuration configuration, Stream str
/// 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 @@ await WithSeekableStreamAsync(
///
/// 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 @@ await WithSeekableStreamAsync(
/// 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 @@ private static T WithSeekableStream(
/// 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 @@ private static async Task WithSeekableStreamAsync(
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 605f5e0da8..fbb3ec2069 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 @@ public void Save(Stream stream, IImageEncoder encoder)
///
/// The stream to save the image to.
/// The encoder to save the image with.
+ /// The token to monitor for cancellation requests.
/// Thrown if the stream or encoder is null.
/// A representing the asynchronous operation.
- public Task SaveAsync(Stream stream, IImageEncoder encoder)
+ 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 @@ public abstract Image CloneAs(Configuration configuration)
/// 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 @@ public EncodeVisitor(IImageEncoder encoder, Stream stream)
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 b227679a67..d40c5c271b 100644
--- a/src/ImageSharp/ImageExtensions.cs
+++ b/src/ImageSharp/ImageExtensions.cs
@@ -5,6 +5,7 @@
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 @@ public static void Save(this Image source, string path)
///
/// 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 @@ public static void Save(this Image source, string path, IImageEncoder encoder)
/// 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 41ecee503f..c3d9618c8c 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -23,7 +23,7 @@
-
+
@@ -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 9d3abc1e82..255193c8ea 100644
--- a/src/ImageSharp/Image{TPixel}.cs
+++ b/src/ImageSharp/Image{TPixel}.cs
@@ -5,6 +5,7 @@
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 @@ internal override void Accept(IImageVisitor visitor)
}
///
- internal override Task AcceptAsync(IImageVisitorAsync visitor)
+ internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken)
{
this.EnsureNotDisposed();
- return visitor.VisitAsync(this);
+ return visitor.VisitAsync(this, cancellationToken);
}
///
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index 912f606b2c..78218aec90 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -4,6 +4,8 @@
using System;
using System.IO;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
@@ -103,7 +105,7 @@ public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider(TestImageProvider provider)
+ public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
provider.LimitAllocatorBufferCapacity().InBytesSqrt(10);
@@ -112,6 +114,60 @@ public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(ex.InnerException);
}
+ [Theory]
+ [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)]
+ [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)]
+ public async Task DecodeAsnc_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ provider.LimitAllocatorBufferCapacity().InBytesSqrt(10);
+ InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder));
+ this.Output.WriteLine(ex.Message);
+ Assert.IsType(ex.InnerException);
+ }
+
+ [Theory]
+ [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, 0)]
+ [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 1)]
+ [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 10)]
+ [InlineData(TestImages.Jpeg.Issues.ExifGetString750Transform, 30)]
+ [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 1)]
+ [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 10)]
+ [InlineData(TestImages.Jpeg.Issues.BadRstProgressive518, 30)]
+ public async Task Decode_IsCancellable(string fileName, int cancellationDelayMs)
+ {
+ // Decoding these huge files took 300ms on i7-8650U in 2020. 30ms should be safe for cancellation delay.
+ string hugeFile = Path.Combine(
+ TestEnvironment.InputImagesDirectoryFullPath,
+ fileName);
+
+ var cts = new CancellationTokenSource();
+ if (cancellationDelayMs == 0)
+ {
+ cts.Cancel();
+ }
+ else
+ {
+ cts.CancelAfter(cancellationDelayMs);
+ }
+
+ await Assert.ThrowsAsync(() => Image.LoadAsync(hugeFile, cts.Token));
+ }
+
+ [Theory(Skip = "Identify is too fast, doesn't work reliably.")]
+ [InlineData(TestImages.Jpeg.Baseline.Exif)]
+ [InlineData(TestImages.Jpeg.Progressive.Bad.ExifUndefType)]
+ public async Task Identify_IsCancellable(string fileName)
+ {
+ string file = Path.Combine(
+ TestEnvironment.InputImagesDirectoryFullPath,
+ fileName);
+
+ var cts = new CancellationTokenSource();
+ cts.CancelAfter(TimeSpan.FromTicks(1));
+ await Assert.ThrowsAsync(() => Image.IdentifyAsync(file, cts.Token));
+ }
+
// DEBUG ONLY!
// The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm"
// into "\tests\Images\ActualOutput\JpegDecoderTests\"
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index 6c9a744633..981270a5fb 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
@@ -1,8 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@@ -284,5 +287,30 @@ public void Encode_PreservesIccProfile()
IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries);
}
+
+ [Theory]
+ [InlineData(JpegSubsample.Ratio420, 0)]
+ [InlineData(JpegSubsample.Ratio420, 3)]
+ [InlineData(JpegSubsample.Ratio420, 10)]
+ [InlineData(JpegSubsample.Ratio444, 0)]
+ [InlineData(JpegSubsample.Ratio444, 3)]
+ [InlineData(JpegSubsample.Ratio444, 10)]
+ public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs)
+ {
+ using var image = new Image(5000, 5000);
+ using MemoryStream stream = new MemoryStream();
+ var cts = new CancellationTokenSource();
+ if (cancellationDelayMs == 0)
+ {
+ cts.Cancel();
+ }
+ else
+ {
+ cts.CancelAfter(cancellationDelayMs);
+ }
+
+ var encoder = new JpegEncoder() { Subsample = subsample };
+ await Assert.ThrowsAsync(() => image.SaveAsync(stream, encoder, cts.Token));
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
index 12e1ec22bd..0dd2abcc1f 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
@@ -71,7 +71,7 @@ public void PostProcess(TestImageProvider provider)
using (var pp = new JpegImagePostProcessor(Configuration.Default, decoder))
using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight))
{
- pp.PostProcess(image.Frames.RootFrame);
+ pp.PostProcess(image.Frames.RootFrame, default);
image.DebugSave(provider);
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
new file mode 100644
index 0000000000..317a5129c4
--- /dev/null
+++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests
+{
+ public partial class ImageTests
+ {
+ public class Decode_Cancellation : ImageLoadTestBase
+ {
+ private bool isTestStreamSeekable;
+ private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0);
+ private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0);
+ private readonly CancellationTokenSource cts = new CancellationTokenSource();
+
+ public Decode_Cancellation()
+ {
+ this.TopLevelConfiguration.StreamProcessingBufferSize = 128;
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable)
+ {
+ this.isTestStreamSeekable = isInputStreamSeekable;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable)
+ {
+ this.isTestStreamSeekable = isInputStreamSeekable;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
+ }
+
+ [Fact]
+ public async Task LoadAsync_Agnostic_Path()
+ {
+ this.isTestStreamSeekable = true;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
+ }
+
+ [Fact]
+ public async Task LoadAsync_Specific_Path()
+ {
+ this.isTestStreamSeekable = true;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task IdentifyAsync_Stream(bool isInputStreamSeekable)
+ {
+ this.isTestStreamSeekable = isInputStreamSeekable;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
+ }
+
+ [Fact]
+ public async Task IdentifyAsync_CustomConfiguration_Path()
+ {
+ this.isTestStreamSeekable = true;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable)
+ {
+ this.isTestStreamSeekable = isInputStreamSeekable;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token));
+ }
+
+ [Fact]
+ public async Task IdentifyWithFormatAsync_CustomConfiguration_Path()
+ {
+ this.isTestStreamSeekable = true;
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token));
+ }
+
+ [Fact]
+ public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream()
+ {
+ _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning);
+
+ await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token));
+ }
+
+ private async Task DoCancel()
+ {
+ // wait until we reach the middle of the steam
+ await this.notifyWaitPositionReachedSemaphore.WaitAsync();
+
+ // set the cancellation
+ this.cts.Cancel();
+
+ // continue processing the stream
+ this.continueSemaphore.Release();
+ }
+
+ protected override Stream CreateStream() => this.TestFormat.CreateAsyncSamaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
index 6327b3eacd..5f3b16806f 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
@@ -24,8 +24,6 @@ public class DetectFormat : ImageLoadTestBase
private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan();
- private byte[] ByteArray => this.DataStream.ToArray();
-
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
private static readonly IImageFormat ExpectedGlobalFormat =
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
index 69b1d21a6d..72de3fcc44 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
@@ -19,9 +19,9 @@ public class Identify : ImageLoadTestBase
{
private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F);
- private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
+ private static readonly Size ExpectedImageSize = new Size(108, 202);
- private byte[] ByteArray => this.DataStream.ToArray();
+ private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
private IImageInfo LocalImageInfo => this.localImageInfoMock.Object;
@@ -35,7 +35,7 @@ public void FromBytes_GlobalConfiguration()
{
IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type);
- Assert.NotNull(info);
+ Assert.Equal(ExpectedImageSize, info.Size());
Assert.Equal(ExpectedGlobalFormat, type);
}
@@ -133,13 +133,45 @@ public async Task FromStreamAsync_GlobalConfiguration()
using (var stream = new MemoryStream(this.ActualImageBytes))
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
- (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(asyncStream);
+ (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
- Assert.NotNull(info.ImageInfo);
- Assert.Equal(ExpectedGlobalFormat, info.Format);
+ Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
+ Assert.Equal(ExpectedGlobalFormat, res.Format);
}
}
+ [Fact]
+ public async Task FromPathAsync_CustomConfiguration()
+ {
+ IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath);
+ Assert.Equal(this.LocalImageInfo, info);
+ }
+
+ [Fact]
+ public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration()
+ {
+ (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath);
+ Assert.NotNull(info.ImageInfo);
+ Assert.Equal(this.LocalImageFormat, info.Format);
+ }
+
+ [Fact]
+ public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration()
+ {
+ (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath);
+
+ Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
+ Assert.Equal(ExpectedGlobalFormat, res.Format);
+ }
+
+ [Fact]
+ public async Task FromPathAsync_GlobalConfiguration()
+ {
+ IImageInfo info = await Image.IdentifyAsync(ActualImagePath);
+
+ Assert.Equal(ExpectedImageSize, info.Size());
+ }
+
[Fact]
public async Task FromStreamAsync_CustomConfiguration()
{
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
index 70d572d60a..44d7daa740 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
@@ -3,7 +3,7 @@
using System;
using System.IO;
-
+using System.Threading;
using Moq;
using SixLabors.ImageSharp.Formats;
@@ -16,6 +16,8 @@ public partial class ImageTests
{
public abstract class ImageLoadTestBase : IDisposable
{
+ private Lazy dataStreamLazy;
+
protected Image localStreamReturnImageRgba32;
protected Image localStreamReturnImageAgnostic;
@@ -46,10 +48,12 @@ public abstract class ImageLoadTestBase : IDisposable
public byte[] Marker { get; }
- public MemoryStream DataStream { get; }
+ public Stream DataStream => this.dataStreamLazy.Value;
public byte[] DecodedData { get; private set; }
+ protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray();
+
protected ImageLoadTestBase()
{
this.localStreamReturnImageRgba32 = new Image(1, 1);
@@ -60,10 +64,9 @@ protected ImageLoadTestBase()
var detector = new Mock();
detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object);
- detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object);
- this.localDecoder = detector.As();
- this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
+ detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object);
+ this.localDecoder = detector.As();
this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny()))
.Callback((c, s) =>
{
@@ -86,6 +89,8 @@ protected ImageLoadTestBase()
})
.Returns(this.localStreamReturnImageAgnostic);
+ this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object);
+
this.LocalConfiguration = new Configuration();
this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector);
this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object);
@@ -93,10 +98,12 @@ protected ImageLoadTestBase()
this.TopLevelConfiguration = new Configuration(this.TestFormat);
this.Marker = Guid.NewGuid().ToByteArray();
- this.DataStream = this.TestFormat.CreateStream(this.Marker);
- this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream);
- this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream);
+ this.dataStreamLazy = new Lazy(this.CreateStream);
+ Stream StreamFactory() => this.DataStream;
+
+ this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory);
+ this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory);
this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object;
this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem;
}
@@ -107,6 +114,8 @@ public void Dispose()
this.localStreamReturnImageRgba32?.Dispose();
this.localStreamReturnImageAgnostic?.Dispose();
}
+
+ protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker);
}
}
}
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
index 77e5679f64..a5034e43b0 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
@@ -25,84 +25,80 @@ private static void VerifyDecodedImage(Image img)
[Fact]
public void Path_Specific()
{
- using (var img = Image.Load(this.Path))
- {
- VerifyDecodedImage(img);
- }
+ using var img = Image.Load(this.Path);
+ VerifyDecodedImage(img);
}
[Fact]
public void Path_Agnostic()
{
- using (var img = Image.Load(this.Path))
- {
- VerifyDecodedImage(img);
- }
+ using var img = Image.Load(this.Path);
+ VerifyDecodedImage(img);
}
[Fact]
public async Task Path_Agnostic_Async()
{
- using (var img = await Image.LoadAsync(this.Path))
- {
- VerifyDecodedImage(img);
- }
+ using var img = await Image.LoadAsync(this.Path);
+ VerifyDecodedImage(img);
+ }
+
+ [Fact]
+ public async Task Path_Specific_Async()
+ {
+ using var img = await Image.LoadAsync