diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs index 3b8641532..e4713e237 100644 --- a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs +++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp { @@ -32,5 +33,10 @@ namespace SixLabors.ImageSharp : base(errorMessage, innerException) { } + + internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException) + : this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException) + { + } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 7e8ac0721..e057db150 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -36,18 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); - } + return decoder.Decode(stream, CreateLargeImageException); } /// @@ -55,46 +45,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); - } + return decoder.DecodeAsync(stream, CreateLargeImageException, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new BmpDecoderCore(configuration, this).Identify(bufferedStream); + return new BmpDecoderCore(configuration, this).Identify(stream, CreateLargeImageException); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new BmpDecoderCore(configuration, this).IdentifyAsync(bufferedStream); + return new BmpDecoderCore(configuration, this).IdentifyAsync(stream, CreateLargeImageException, cancellationToken); + } + + private static InvalidImageContentException CreateLargeImageException(InvalidMemoryOperationException ex, Size dims) + { + return new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index ea8fd11a8..6f9223637 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -118,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { try @@ -197,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadImageHeaders(stream, out _, out _); return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 2a5fde6ac..206dd6f2f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -30,21 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -52,30 +39,17 @@ namespace SixLabors.ImageSharp.Formats.Gif => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - GifThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -85,18 +59,18 @@ namespace SixLabors.ImageSharp.Formats.Gif var decoder = new GifDecoderCore(configuration, this); using var bufferedStream = new BufferedReadStream(stream); - return decoder.Identify(bufferedStream); + return decoder.Identify(bufferedStream, default); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); using var bufferedStream = new BufferedReadStream(stream); - return decoder.IdentifyAsync(bufferedStream); + return await decoder.IdentifyAsync(bufferedStream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 78ffee8bd..8f5cc3b5c 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Image image = null; @@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { try { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 97886e526..b55f1119b 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; @@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Formats /// The pixel format. /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task> DecodeAsync(Configuration configuration, Stream stream) + Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// @@ -48,8 +50,9 @@ namespace SixLabors.ImageSharp.Formats /// /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Task DecodeAsync(Configuration configuration, Stream stream); + Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs index 33748bf24..e190f7add 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; @@ -17,21 +18,36 @@ namespace SixLabors.ImageSharp.Formats /// Configuration Configuration { get; } + /// + /// Gets the dimensions of the image being decoded. + /// + Size Dimensions { get; } + /// /// Decodes the image from the specified stream. /// /// The pixel format. /// The stream, where the image should be decoded from. Cannot be null. + /// The token to monitor for cancellation requests. /// is null. /// The decoded image. - Image Decode(BufferedReadStream stream) + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// /// Reads the raw image information from the specified stream. /// /// The containing image data. + /// The token to monitor for cancellation requests. /// The . - IImageInfo Identify(BufferedReadStream stream); + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 862c64999..6f5fc2333 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats @@ -24,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats /// /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The object - Task IdentifyAsync(Configuration configuration, Stream stream); + Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 9d1639a09..a7ade5e24 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -14,21 +17,152 @@ namespace SixLabors.ImageSharp.Formats /// Reads the raw image information from the specified stream. /// /// The decoder. - /// The containing image data. + /// The containing image data. + /// The token to monitor for cancellation requests. /// is null. /// A representing the asynchronous operation. - public static Task IdentifyAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) - => Task.FromResult(decoder.Identify(stream)); + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Stream stream, + CancellationToken cancellationToken) + => decoder.IdentifyAsync(stream, DefaultLargeImageExceptionFactory, cancellationToken); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// is null. + /// A representing the asynchronous operation. + public static Task IdentifyAsync( + this IImageDecoderInternals decoder, + Stream stream, + Func tooLargeImageExceptionFactory, + CancellationToken cancellationToken) + { + try + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + IImageInfo imageInfo = decoder.Identify(bufferedReadStream, cancellationToken); + return Task.FromResult(imageInfo); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = tooLargeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } /// /// Decodes the image from the specified stream. /// /// The pixel format. /// The decoder. - /// The containing image data. + /// The containing image data. + /// The token to monitor for cancellation requests. /// A representing the asynchronous operation. - public static Task> DecodeAsync(this IImageDecoderInternals decoder, BufferedReadStream stream) + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => + decoder.DecodeAsync( + stream, + DefaultLargeImageExceptionFactory, + cancellationToken); + + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The decoder. + /// The containing image data. + /// Factory method to handle as . + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public static Task> DecodeAsync( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => Task.FromResult(decoder.Decode(stream)); + { + try + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + Image image = decoder.Decode(bufferedReadStream, cancellationToken); + return Task.FromResult(image); + } + catch (InvalidMemoryOperationException ex) + { + InvalidImageContentException invalidImageContentException = largeImageExceptionFactory(ex, decoder.Dimensions); + return Task.FromException>(invalidImageContentException); + } + catch (OperationCanceledException) + { + return Task.FromCanceled>(cancellationToken); + } + catch (Exception ex) + { + return Task.FromException>(ex); + } + } + + public static IImageInfo Identify(this IImageDecoderInternals decoder, Stream stream) + => decoder.Identify(stream, DefaultLargeImageExceptionFactory); + + public static IImageInfo Identify( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory) + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + + try + { + return decoder.Identify(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); + } + } + + public static Image Decode(this IImageDecoderInternals decoder, Stream stream) + where TPixel : unmanaged, IPixel + => decoder.Decode(stream, DefaultLargeImageExceptionFactory); + + public static Image Decode( + this IImageDecoderInternals decoder, + Stream stream, + Func largeImageExceptionFactory) + where TPixel : unmanaged, IPixel + { + using BufferedReadStream bufferedReadStream = new BufferedReadStream(stream); + + try + { + return decoder.Decode(bufferedReadStream, default); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); + } + } + + private static InvalidImageContentException DefaultLargeImageExceptionFactory( + InvalidMemoryOperationException memoryOperationException, + Size dimensions) => + new InvalidImageContentException(dimensions, memoryOperationException); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index c5332acb5..106c1f597 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -24,20 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); - - JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -45,31 +33,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); - - JpegThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -77,20 +52,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - - return decoder.Identify(bufferedStream); + return decoder.Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - - return decoder.IdentifyAsync(bufferedStream); + return decoder.IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2956d2c11..6874d09e9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -117,6 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Size ImageSizeInPixels { get; private set; } + /// + Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels; + /// /// Gets the number of MCU blocks in the image as . /// @@ -205,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { this.ParseStream(stream); @@ -217,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ParseStream(stream, true); this.InitExifProfile(); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 9eb927784..677a2003e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -22,65 +23,36 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - PngThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream) { var decoder = new PngDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Identify(bufferedStream); + return decoder.Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { var decoder = new PngDecoderCore(configuration, this); - using var bufferedStream = new BufferedReadStream(stream); - return decoder.IdentifyAsync(bufferedStream); + return decoder.IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 89fa4e63d..b751a704a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -9,6 +9,7 @@ using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Png public Size Dimensions => new Size(this.header.Width, this.header.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); @@ -224,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { var metadata = new ImageMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata(); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 3d9b9a3d2..bcf52a011 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -21,21 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return decoder.Decode(bufferedStream); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.Decode(stream); } /// @@ -43,49 +30,33 @@ namespace SixLabors.ImageSharp.Formats.Tga => this.Decode(configuration, stream); /// - public async Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(stream); - return await decoder.DecodeAsync(bufferedStream).ConfigureAwait(false); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - TgaThrowHelper.ThrowInvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - - // Not reachable, as the previous statement will throw a exception. - return null; - } + return decoder.DecodeAsync(stream, cancellationToken); } /// - public async Task DecodeAsync(Configuration configuration, Stream stream) - => await this.DecodeAsync(configuration, stream).ConfigureAwait(false); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new TgaDecoderCore(configuration, this).Identify(bufferedStream); + return new TgaDecoderCore(configuration, this).Identify(stream); } /// - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var bufferedStream = new BufferedReadStream(stream); - return new TgaDecoderCore(configuration, this).IdentifyAsync(bufferedStream); + return new TgaDecoderCore(configuration, this).IdentifyAsync(stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 7cd83fedb..eef6e7362 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tga public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); /// - public Image Decode(BufferedReadStream stream) + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { try @@ -640,7 +641,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public IImageInfo Identify(BufferedReadStream stream) + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadFileHeader(stream); return new ImageInfo( diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index bc44cd8ca..ff4886a10 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -163,13 +163,15 @@ namespace SixLabors.ImageSharp private static async Task<(Image Image, IImageFormat Format)> DecodeAsync(Stream stream, Configuration config, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); + (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) + .ConfigureAwait(false); if (decoder is null) { return (null, null); } - Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); + Image img = await decoder.DecodeAsync(config, stream, cancellationToken) + .ConfigureAwait(false); return (img, format); } @@ -193,7 +195,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = await decoder.DecodeAsync(config, stream).ConfigureAwait(false); + Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false); return (img, format); } @@ -242,7 +244,7 @@ namespace SixLabors.ImageSharp return (null, format); } - IImageInfo info = await detector.IdentifyAsync(config, stream).ConfigureAwait(false); + IImageInfo info = await detector.IdentifyAsync(config, stream, cancellationToken).ConfigureAwait(false); return (info, format); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index d005873ca..95f775d84 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp return WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s), + (s, ct) => decoder.DecodeAsync(configuration, s, ct), cancellationToken); } @@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( Configuration.Default, stream, - (s, ct) => decoder.DecodeAsync(Configuration.Default, s), + (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct), cancellationToken); /// @@ -577,7 +577,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s), + (s, ct) => decoder.DecodeAsync(configuration, s, ct), cancellationToken); /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index cd5026117..97a2a332d 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -22,7 +22,7 @@ - + diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 70d572d60..8c9f0994f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); - detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); + detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), TODO)).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index a883039f2..4c62c101c 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -219,7 +220,8 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public Task> DecodeAsync(Configuration config, Stream stream) + public Task> DecodeAsync(Configuration config, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(config, stream)); @@ -227,7 +229,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } public class TestEncoder : ImageSharp.Formats.IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index fae3ff5a5..f641c579e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using SixLabors.ImageSharp.Formats; @@ -46,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -82,6 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index ae6e2e3f1..62318bd86 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; @@ -14,7 +15,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => Task.FromResult(this.Decode(configuration, stream)); @@ -48,7 +50,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task IdentifyAsync(Configuration configuration, Stream stream) + public Task IdentifyAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => Task.FromResult(this.Identify(configuration, stream)); public IImageInfo Identify(Configuration configuration, Stream stream) @@ -62,6 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 978c0555c..979b57a63 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -371,7 +372,8 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -391,7 +393,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } private class TestDecoderWithParameters : IImageDecoder @@ -425,7 +428,8 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream) + public Task> DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCountsAsync[this.callerName]++; @@ -445,7 +449,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); - public async Task DecodeAsync(Configuration configuration, Stream stream) => await this.DecodeAsync(configuration, stream); + public async Task DecodeAsync(Configuration configuration, Stream stream, + CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, TODO); } } }