From 4802b4f0f4c26fc6bbc30266d02734b5269c1600 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Mon, 14 Feb 2022 16:01:37 -0800 Subject: [PATCH 01/22] Always pre-copy the source stream into an in-memory stream so that the subsequent image loading operations can safely use synchronous IO --- src/ImageSharp/Image.FromStream.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index f5e32d8ce0..43f4449c53 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -770,20 +770,6 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } - // To make sure we don't trigger anything with aspnetcore then we just need to make sure we are - // seekable and we make the copy using CopyToAsync if the stream is seekable then we aren't using - // one of the aspnetcore wrapped streams that error on sync api calls and we can use it without - // having to further wrap - if (stream.CanSeek) - { - if (configuration.ReadOrigin == ReadOrigin.Begin) - { - stream.Position = 0; - } - - return await action(stream, cancellationToken).ConfigureAwait(false); - } - using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator); await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; From 3a11b2f3820bc862dbdf55beeae5432e9a5b7ccd Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Tue, 15 Feb 2022 18:30:02 -0800 Subject: [PATCH 02/22] Plumb CancellationToken through IImageDecoder and IImageInfoDetector methods --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 12 +++--- src/ImageSharp/Formats/Gif/GifDecoder.cs | 12 +++--- src/ImageSharp/Formats/IImageDecoder.cs | 6 ++- src/ImageSharp/Formats/IImageInfoDetector.cs | 3 +- .../Formats/ImageDecoderUtilities.cs | 18 ++++++--- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 12 +++--- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 12 +++--- src/ImageSharp/Formats/Png/PngDecoder.cs | 40 +++++++++---------- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 12 +++--- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 10 ++--- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 10 ++--- src/ImageSharp/Image.Decode.cs | 14 ++++--- .../Image/ImageTests.ImageLoadTestBase.cs | 6 +-- ...d_FileSystemPath_PassLocalConfiguration.cs | 4 +- ....Load_FromStream_PassLocalConfiguration.cs | 4 +- tests/ImageSharp.Tests/TestFormat.cs | 10 ++--- .../ReferenceCodecs/MagickReferenceDecoder.cs | 6 +-- .../SystemDrawingReferenceDecoder.cs | 10 ++--- .../Tests/TestImageProviderTests.cs | 8 ++-- 19 files changed, 110 insertions(+), 99 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 129b3a1aa0..1417909b4a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -31,18 +31,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -60,11 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(configuration, stream); + return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 196d77ad77..e00c272e1d 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -27,16 +27,16 @@ namespace SixLabors.ImageSharp.Formats.Gif public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -52,14 +52,14 @@ namespace SixLabors.ImageSharp.Formats.Gif .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); using var bufferedStream = new BufferedReadStream(configuration, stream); - return decoder.Identify(bufferedStream, default); + return decoder.Identify(bufferedStream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index b55f1119b3..1e673b30c4 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -19,9 +19,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) - Image Decode(Configuration configuration, Stream stream) + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel; /// @@ -29,9 +30,10 @@ 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) - Image Decode(Configuration configuration, Stream stream); + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); /// /// Decodes the image from the specified stream to an of a specific pixel type. diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 6f5fc23338..920e313744 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -17,8 +17,9 @@ namespace SixLabors.ImageSharp.Formats /// /// The configuration for the image. /// The containing image data. + /// The token to monitor for cancellation requests. /// The object - IImageInfo Identify(Configuration configuration, Stream stream); + IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); /// /// Reads the raw image information from the specified stream. diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 5d77fb0c8c..13363fb64b 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -130,13 +130,14 @@ namespace SixLabors.ImageSharp.Formats public static IImageInfo Identify( this IImageDecoderInternals decoder, Configuration configuration, - Stream stream) + Stream stream, + CancellationToken cancellationToken = default) { using var bufferedReadStream = new BufferedReadStream(configuration, stream); try { - return decoder.Identify(bufferedReadStream, default); + return decoder.Identify(bufferedReadStream, cancellationToken); } catch (InvalidMemoryOperationException ex) { @@ -144,22 +145,27 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode(this IImageDecoderInternals decoder, Configuration configuration, Stream stream) + public static Image Decode( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); public static Image Decode( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - Func largeImageExceptionFactory) + Func largeImageExceptionFactory, + CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream); try { - return decoder.Decode(bufferedReadStream, default); + return decoder.Decode(bufferedReadStream, cancellationToken); } catch (InvalidMemoryOperationException ex) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 18212ffc7b..37b8fdd1fb 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -17,18 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Identify(configuration, stream); + return decoder.Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 2eebbb1d93..f227726d18 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -30,18 +30,18 @@ namespace SixLabors.ImageSharp.Formats.Pbm public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new PbmDecoderCore(configuration); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -59,12 +59,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); var decoder = new PbmDecoderCore(configuration); - return decoder.Identify(configuration, stream); + return decoder.Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 04e70c51d0..b0764e0405 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -17,18 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Png public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { PngDecoderCore decoder = new(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { PngDecoderCore decoder = new(configuration, true); - IImageInfo info = decoder.Identify(configuration, stream); + IImageInfo info = decoder.Identify(configuration, stream, cancellationToken); stream.Position = 0; PngMetadata meta = info.Metadata.GetPngMetadata(); @@ -40,41 +40,41 @@ namespace SixLabors.ImageSharp.Formats.Png if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.Rgb: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.Palette: - return this.Decode(configuration, stream); + return this.Decode(configuration, stream, cancellationToken); case PngColorType.GrayscaleWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); case PngColorType.RgbWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream); + ? this.Decode(configuration, stream, cancellationToken) + : this.Decode(configuration, stream, cancellationToken); default: - return this.Decode(configuration, stream); + return this.Decode(configuration, stream, cancellationToken); } } @@ -141,10 +141,10 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { PngDecoderCore decoder = new(configuration, this); - return decoder.Identify(configuration, stream); + return decoder.Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index e06a0ee887..675faac610 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tga public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) - => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -45,11 +45,11 @@ namespace SixLabors.ImageSharp.Formats.Tga .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).Identify(configuration, stream); + return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 9d52e34dfe..75485e400a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -19,17 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, "stream"); var decoder = new TiffDecoderCore(configuration, this); - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) @@ -47,12 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff .ConfigureAwait(false); /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); var decoder = new TiffDecoderCore(configuration, this); - return decoder.Identify(configuration, stream); + return decoder.Identify(configuration, stream, cancellationToken); } /// diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index b4e6cecd0c..63b0fa505d 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Webp try { - return decoder.Decode(configuration, stream); + return decoder.Decode(configuration, stream, cancellationToken); } catch (InvalidMemoryOperationException ex) { @@ -41,15 +41,15 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { Guard.NotNull(stream, nameof(stream)); - return new WebpDecoderCore(configuration, this).Identify(configuration, stream); + return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index ee340bf86e..75dbd6c5c8 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -148,11 +148,12 @@ namespace SixLabors.ImageSharp /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// The pixel format. /// /// A new . /// - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -161,7 +162,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = decoder.Decode(config, stream); + Image img = decoder.Decode(config, stream, cancellationToken); return (img, format); } @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp return (img, format); } - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config) + private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) @@ -199,7 +200,7 @@ namespace SixLabors.ImageSharp return (null, null); } - Image img = decoder.Decode(config, stream); + Image img = decoder.Decode(config, stream, cancellationToken); return (img, format); } @@ -220,10 +221,11 @@ namespace SixLabors.ImageSharp /// /// The stream. /// the configuration. + /// The token to monitor for cancellation requests. /// /// The or null if a suitable info detector is not found. /// - private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -232,7 +234,7 @@ namespace SixLabors.ImageSharp return (null, null); } - IImageInfo info = detector?.Identify(config, stream); + IImageInfo info = detector?.Identify(config, stream, cancellationToken); return (info, format); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 44d7daa740..8c4c425b47 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -63,11 +63,11 @@ namespace SixLabors.ImageSharp.Tests this.localImageFormatMock = new Mock(); var detector = new Mock(); - detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((c, s) => { using (var ms = new MemoryStream()) @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests }) .Returns(this.localStreamReturnImageRgba32); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((c, s) => { using (var ms = new MemoryStream()) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 72477a832a..0d5eead4bf 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); } [Fact] @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs index 17b557f833..b08a82523c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); } [Fact] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream)); + this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); } [Fact] diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 6c2b97eb62..c879e66da0 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -212,9 +212,9 @@ namespace SixLabors.ImageSharp.Tests public int HeaderSize => this.testFormat.HeaderSize; - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream, default).GetAwaiter().GetResult(); + => this.DecodeImpl(configuration, stream, cancellationToken).GetAwaiter().GetResult(); public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -239,13 +239,13 @@ namespace SixLabors.ImageSharp.Tests public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); - public IImageInfo Identify(Configuration configuration, Stream stream) => - this.IdentifyAsync(configuration, stream, default).GetAwaiter().GetResult(); + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) => + this.IdentifyAsync(configuration, stream, cancellationToken).GetAwaiter().GetResult(); public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeImpl(configuration, stream, cancellationToken); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index f0834dc001..2d1c616c8b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -61,9 +61,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - => Task.FromResult(this.Decode(configuration, stream)); + => Task.FromResult(this.Decode(configuration, stream, cancellationToken)); - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { var bmpReadDefines = new BmpReadDefines @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new Image(configuration, new ImageMetadata(), framesList); } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 1eb1328ef2..3bc6fef297 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -17,9 +17,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => Task.FromResult(this.Decode(configuration, stream)); + => Task.FromResult(this.Decode(configuration, stream, cancellationToken)); - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) @@ -50,9 +50,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => Task.FromResult(this.Identify(configuration, stream)); + => Task.FromResult(this.Identify(configuration, stream, cancellationToken)); - public IImageInfo Identify(Configuration configuration, Stream stream) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) { @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 129d17f4df..0ed57994d7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -365,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; @@ -390,7 +390,7 @@ namespace SixLabors.ImageSharp.Tests InvocationCountsAsync[name] = 0; } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); @@ -420,7 +420,7 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; @@ -445,7 +445,7 @@ namespace SixLabors.ImageSharp.Tests InvocationCountsAsync[name] = 0; } - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) => await this.DecodeAsync(configuration, stream, cancellationToken); From f874218d0b5a2cf064aa0a414f333381e9a22127 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Tue, 15 Feb 2022 21:04:15 -0800 Subject: [PATCH 03/22] Change WithSeekableStreamAsync() so that it executes synchronous actions after preloading the stream, rather than async actions (since the async actions all ultimately operate synchronously anyway) --- src/ImageSharp/Image.Decode.cs | 100 ----------------------------- src/ImageSharp/Image.FromStream.cs | 18 +++--- 2 files changed, 9 insertions(+), 109 deletions(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 75dbd6c5c8..9bb680c82a 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -2,11 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; -using System.Linq; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -96,20 +93,6 @@ namespace SixLabors.ImageSharp return format; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The image stream to read the header from. - /// The configuration. - /// The mime type or null if none found. - private static Task InternalDetectFormatAsync(Stream stream, Configuration config) - { - // We are going to cheat here because we know that by this point we have been wrapped in a - // seekable stream then we are free to use sync APIs this is potentially brittle and may - // need a better fix in the future. - return Task.FromResult(InternalDetectFormat(stream, config)); - } - /// /// By reading the header on the provided stream this calculates the images format. /// @@ -126,23 +109,6 @@ namespace SixLabors.ImageSharp : null; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The image stream to read the header from. - /// The configuration. - /// The decoder and the image format or null if none found. - private static async Task<(IImageDecoder Decoder, IImageFormat Format)> DiscoverDecoderAsync(Stream stream, Configuration config) - { - IImageFormat format = await InternalDetectFormatAsync(stream, config).ConfigureAwait(false); - - IImageDecoder decoder = format != null - ? config.ImageFormatsManager.FindDecoder(format) - : null; - - return (decoder, format); - } - /// /// Decodes the image stream to the current image. /// @@ -166,32 +132,6 @@ namespace SixLabors.ImageSharp return (img, format); } - /// - /// Decodes the image stream to the current image. - /// - /// 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, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config) - .ConfigureAwait(false); - if (decoder is null) - { - return (null, null); - } - - Image img = await decoder.DecodeAsync(config, stream, cancellationToken) - .ConfigureAwait(false); - return (img, format); - } - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); @@ -204,18 +144,6 @@ namespace SixLabors.ImageSharp return (img, format); } - 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) - { - return (null, null); - } - - Image img = await decoder.DecodeAsync(config, stream, cancellationToken).ConfigureAwait(false); - return (img, format); - } - /// /// Reads the raw image information from the specified stream. /// @@ -237,33 +165,5 @@ namespace SixLabors.ImageSharp IImageInfo info = detector?.Identify(config, stream, cancellationToken); return (info, format); } - - /// - /// Reads the raw image information from the specified stream. - /// - /// 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, CancellationToken cancellationToken) - { - (IImageDecoder decoder, IImageFormat format) = await DiscoverDecoderAsync(stream, config).ConfigureAwait(false); - - if (!(decoder is IImageInfoDetector detector)) - { - return (null, null); - } - - if (detector is null) - { - return (null, format); - } - - 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 43f4449c53..8260caf56f 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, _) => InternalDetectFormatAsync(s, configuration), + (s, _) => InternalDetectFormat(s, configuration), cancellationToken); /// @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => InternalIdentityAsync(s, configuration ?? Configuration.Default, ct), + (s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct), cancellationToken); /// @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp return WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s, ct), + (s, ct) => decoder.Decode(configuration, s, ct), cancellationToken); } @@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( Configuration.Default, stream, - (s, ct) => decoder.DecodeAsync(Configuration.Default, s, ct), + (s, ct) => decoder.Decode(Configuration.Default, s, ct), cancellationToken); /// @@ -511,7 +511,7 @@ namespace SixLabors.ImageSharp => WithSeekableStreamAsync( configuration, stream, - (s, ct) => decoder.DecodeAsync(configuration, s, ct), + (s, ct) => decoder.Decode(configuration, s, ct), cancellationToken); /// @@ -586,7 +586,7 @@ namespace SixLabors.ImageSharp (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( configuration, stream, - async (s, ct) => await DecodeAsync(s, configuration, ct).ConfigureAwait(false), + (s, ct) => Decode(s, configuration, ct), cancellationToken) .ConfigureAwait(false); @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp await WithSeekableStreamAsync( configuration, stream, - (s, ct) => DecodeAsync(s, configuration, ct), + (s, ct) => Decode(s, configuration, ct), cancellationToken) .ConfigureAwait(false); @@ -759,7 +759,7 @@ namespace SixLabors.ImageSharp private static async Task WithSeekableStreamAsync( Configuration configuration, Stream stream, - Func> action, + Func action, CancellationToken cancellationToken) { Guard.NotNull(configuration, nameof(configuration)); @@ -774,7 +774,7 @@ namespace SixLabors.ImageSharp await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; - return await action(memoryStream, cancellationToken).ConfigureAwait(false); + return action(memoryStream, cancellationToken); } } } From eb7b9d9f9b60a7928c8724333b470d74e47ea930 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Wed, 16 Feb 2022 09:19:01 -0800 Subject: [PATCH 04/22] Remove IImageDecoder.DecodeAsync() and IImageInfoDetector.IdentifyAsync() --- src/ImageSharp/Advanced/AotCompilerTools.cs | 1 - src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 26 ---- src/ImageSharp/Formats/Gif/GifDecoder.cs | 24 ---- src/ImageSharp/Formats/IImageDecoder.cs | 23 ---- src/ImageSharp/Formats/IImageInfoDetector.cs | 10 -- .../Formats/ImageDecoderUtilities.cs | 115 ------------------ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 31 ----- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 25 ---- src/ImageSharp/Formats/Png/PngDecoder.cs | 70 ----------- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 26 ---- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 25 ---- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 36 ------ .../Image/ImageTests.ImageLoadTestBase.cs | 1 - tests/ImageSharp.Tests/TestFormat.cs | 17 +-- .../ReferenceCodecs/MagickReferenceDecoder.cs | 8 -- .../SystemDrawingReferenceDecoder.cs | 12 -- .../Tests/TestImageProviderTests.cs | 33 ----- 17 files changed, 4 insertions(+), 479 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 82a146dc72..504361f60a 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -288,7 +288,6 @@ namespace SixLabors.ImageSharp.Advanced where TDecoder : class, IImageDecoder { default(TDecoder).Decode(default, default); - default(TDecoder).DecodeAsync(default, default, default); } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 1417909b4a..f1170bf213 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -3,9 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -44,21 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new BmpDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -66,13 +48,5 @@ namespace SixLabors.ImageSharp.Formats.Bmp return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new BmpDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index e00c272e1d..8c9411ac4c 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,9 +3,7 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -38,19 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Gif public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var decoder = new GifDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -61,14 +46,5 @@ namespace SixLabors.ImageSharp.Formats.Gif using var bufferedStream = new BufferedReadStream(configuration, stream); return decoder.Identify(bufferedStream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new GifDecoderCore(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 1e673b30c4..2b8c5131a5 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats @@ -34,27 +33,5 @@ namespace SixLabors.ImageSharp.Formats /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// 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, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an . - /// - /// 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, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 920e313744..c20894eeac 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; namespace SixLabors.ImageSharp.Formats { @@ -20,14 +19,5 @@ namespace SixLabors.ImageSharp.Formats /// The token to monitor for cancellation requests. /// The object IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); - - /// - /// Reads the raw image information from the specified stream. - /// - /// The configuration for the image. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object - Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 13363fb64b..69b2dd9300 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -13,120 +12,6 @@ namespace SixLabors.ImageSharp.Formats { internal static class ImageDecoderUtilities { - /// - /// Reads the raw image information from the specified stream. - /// - /// The decoder. - /// /// The configuration for the image. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// is null. - /// A representing the asynchronous operation. - public static Task IdentifyAsync( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - => decoder.IdentifyAsync(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 configuration for the image. - /// The containing image data. - /// Factory method to handle as . - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task> DecodeAsync( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - Func largeImageExceptionFactory, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - 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, diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 37b8fdd1fb..e2d5b54c39 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -30,21 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - 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); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -53,20 +37,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg using var decoder = new JpegDecoderCore(configuration, this); return decoder.Identify(configuration, stream, cancellationToken); } - - /// - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - // 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/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index f227726d18..43847cf9b2 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm @@ -43,21 +42,6 @@ namespace SixLabors.ImageSharp.Formats.Pbm public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new PbmDecoderCore(configuration); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -66,14 +50,5 @@ namespace SixLabors.ImageSharp.Formats.Pbm var decoder = new PbmDecoderCore(configuration); return decoder.Identify(configuration, stream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new PbmDecoderCore(configuration); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index b0764e0405..b5cbc1ccca 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -78,80 +77,11 @@ namespace SixLabors.ImageSharp.Formats.Png } } - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - PngDecoderCore decoder = new(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - PngDecoderCore decoder = new(configuration, true); - IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - stream.Position = 0; - - PngMetadata meta = info.Metadata.GetPngMetadata(); - PngColorType color = meta.ColorType.GetValueOrDefault(); - PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); - switch (color) - { - case PngColorType.Grayscale: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.Rgb: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - - return !meta.HasTransparency - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.Palette: - return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.GrayscaleWithAlpha: - return (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - case PngColorType.RgbWithAlpha: - return (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - default: - return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - } - } - /// public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) { PngDecoderCore decoder = new(configuration, this); return decoder.Identify(configuration, stream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - PngDecoderCore decoder = new(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 675faac610..933dba00a8 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -3,9 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tga @@ -29,21 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Tga public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new TgaDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -51,13 +33,5 @@ namespace SixLabors.ImageSharp.Formats.Tga return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new TgaDecoderCore(configuration, this).IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 75485e400a..0ada90e90b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -31,21 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new TiffDecoderCore(configuration, this); - return decoder.DecodeAsync(configuration, stream, cancellationToken); - } - - /// - 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, CancellationToken cancellationToken = default) { @@ -54,14 +38,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff var decoder = new TiffDecoderCore(configuration, this); return decoder.Identify(configuration, stream, cancellationToken); } - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new TiffDecoderCore(configuration, this); - return decoder.IdentifyAsync(configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index 63b0fa505d..5757f65358 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -50,39 +48,5 @@ namespace SixLabors.ImageSharp.Formats.Webp /// public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); - - /// - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - var decoder = new WebpDecoderCore(configuration, this); - - try - { - using var bufferedStream = new BufferedReadStream(configuration, stream); - return decoder.DecodeAsync(configuration, bufferedStream, cancellationToken); - } - 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}.", ex); - } - } - - /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); - - /// - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - using var bufferedStream = new BufferedReadStream(configuration, stream); - return new WebpDecoderCore(configuration, this).IdentifyAsync(configuration, bufferedStream, cancellationToken); - } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 8c4c425b47..fd743093ef 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -64,7 +64,6 @@ namespace SixLabors.ImageSharp.Tests var detector = new Mock(); detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); - detector.Setup(x => x.IdentifyAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(this.localImageInfoMock.Object); this.localDecoder = detector.As(); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index c879e66da0..efe9585521 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -214,17 +214,14 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream, cancellationToken).GetAwaiter().GetResult(); + => this.DecodeImpl(configuration, stream); - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream, cancellationToken); - private async Task> DecodeImpl(Configuration config, Stream stream, CancellationToken cancellationToken) + private Image DecodeImpl(Configuration config, Stream stream) where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); - await stream.CopyToAsync(ms, config.StreamProcessingBufferSize, cancellationToken); + stream.CopyTo(ms, config.StreamProcessingBufferSize); byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { @@ -241,14 +238,8 @@ namespace SixLabors.ImageSharp.Tests public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) => - this.IdentifyAsync(configuration, stream, cancellationToken).GetAwaiter().GetResult(); - - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeImpl(configuration, stream, cancellationToken); + this.DecodeImpl(configuration, stream); } public class TestEncoder : IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 2d1c616c8b..a3a7d62d8f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; @@ -59,10 +58,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - => Task.FromResult(this.Decode(configuration, stream, cancellationToken)); - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { @@ -106,8 +101,5 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 3bc6fef297..1108144800 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -15,10 +13,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => Task.FromResult(this.Decode(configuration, stream, cancellationToken)); - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -49,9 +43,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } - public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => Task.FromResult(this.Identify(configuration, stream, cancellationToken)); - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) @@ -62,8 +53,5 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 0ed57994d7..4542257c61 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Concurrent; using System.IO; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -350,9 +349,6 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InvocationCountsAsync = - new ConcurrentDictionary(); - private static readonly object Monitor = new object(); private string callerName; @@ -372,28 +368,15 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCountsAsync[this.callerName]++; - return Task.FromResult(new Image(42, 42)); - } - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; - internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; - internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; - InvocationCountsAsync[name] = 0; } public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); } private class TestDecoderWithParameters : IImageDecoder @@ -401,9 +384,6 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InvocationCountsAsync = - new ConcurrentDictionary(); - private static readonly object Monitor = new object(); private string callerName; @@ -427,28 +407,15 @@ namespace SixLabors.ImageSharp.Tests return new Image(42, 42); } - public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCountsAsync[this.callerName]++; - return Task.FromResult(new Image(42, 42)); - } - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; - internal static int GetInvocationCountAsync(string callerName) => InvocationCountsAsync[callerName]; - internal void InitCaller(string name) { this.callerName = name; InvocationCounts[name] = 0; - InvocationCountsAsync[name] = 0; } public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken); } } } From 1c225e93b808131b8562c669295316df114a9d1d Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Wed, 16 Feb 2022 15:12:44 -0800 Subject: [PATCH 05/22] Make the cancellationToken arguments required --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 6 +++--- src/ImageSharp/Formats/Gif/GifDecoder.cs | 6 +++--- src/ImageSharp/Formats/IImageDecoder.cs | 4 ++-- src/ImageSharp/Formats/IImageInfoDetector.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 6 +++--- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 6 +++--- src/ImageSharp/Formats/Png/PngDecoder.cs | 6 +++--- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 6 +++--- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 6 +++--- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 6 +++--- src/ImageSharp/Image.FromStream.cs | 6 +++--- .../Codecs/Jpeg/DecodeJpeg.cs | 2 +- .../Codecs/Jpeg/IdentifyJpeg.cs | 2 +- .../Formats/Bmp/BmpDecoderTests.cs | 2 +- .../Formats/Gif/GifDecoderTests.cs | 2 +- .../Formats/Gif/GifMetadataTests.cs | 10 +++++----- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 14 +++++++------- .../Formats/Png/PngDecoderTests.Chunks.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 2 +- .../Formats/Png/PngMetadataTests.cs | 8 ++++---- 21 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 504361f60a..2323b5ba78 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -287,7 +287,7 @@ namespace SixLabors.ImageSharp.Advanced where TPixel : unmanaged, IPixel where TDecoder : class, IImageDecoder { - default(TDecoder).Decode(default, default); + default(TDecoder).Decode(default, default, default); } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index f1170bf213..e764489388 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -38,11 +38,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 8c9411ac4c..9d4c882134 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Gif public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); @@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 2b8c5131a5..db7f64ee26 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// @@ -32,6 +32,6 @@ namespace SixLabors.ImageSharp.Formats /// The token to monitor for cancellation requests. /// The . // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); + Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index c20894eeac..c6377edd0b 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -18,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats /// The containing image data. /// The token to monitor for cancellation requests. /// The object - IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default); + IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index e2d5b54c39..22a9801b8f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -26,11 +26,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 43847cf9b2..97a9cb7d75 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -39,11 +39,11 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index b5cbc1ccca..0b233848ad 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Png public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { PngDecoderCore decoder = new(configuration, this); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) { PngDecoderCore decoder = new(configuration, true); IImageInfo info = decoder.Identify(configuration, stream, cancellationToken); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { PngDecoderCore decoder = new(configuration, this); return decoder.Identify(configuration, stream, cancellationToken); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 933dba00a8..bb0a0d5489 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tga public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector { /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -23,11 +23,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 0ada90e90b..64140f5aa3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, "stream"); @@ -28,10 +28,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index 5757f65358..e076805e41 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Webp public bool IgnoreMetadata { get; set; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); @@ -47,6 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => this.Decode(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 8260caf56f..0f3203b1fc 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) { Guard.NotNull(decoder, nameof(decoder)); - return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); } /// @@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); + => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s, default)); /// /// Create a new instance of the class from the given stream. @@ -486,7 +486,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) where TPixel : unmanaged, IPixel - => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); + => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); /// /// Create a new instance of the class from the given stream. diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 9665ca42d6..d64eb15ae4 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private void GenericBechmark() { this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream); + using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default); } [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index aae144ce02..7bb147cf91 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using var memoryStream = new MemoryStream(this.jpegBytes); var decoder = new JpegDecoder(); - return decoder.Identify(Configuration.Default, memoryStream); + return decoder.Identify(Configuration.Default, memoryStream, default); } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index f85bc78fc0..71d0ab34a4 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -557,7 +557,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new BmpDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index c8ecdb717b..6fbdaf30ba 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - using (Image image = GifDecoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream, default)) { Assert.Equal((200, 200), (image.Width, image.Height)); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 5699b47418..efabed5b29 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif input.Save(memoryStream, new GifEncoder()); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(2, metadata.Comments.Count); @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); } @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 840cc9f685..f126825ed6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - using (Image image = JpegDecoder.Decode(Configuration.Default, stream)) + using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default)) { JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { - IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream); + IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType); } @@ -181,8 +181,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (var stream = new MemoryStream(testFile.Bytes, false)) { IImageInfo imageInfo = useIdentify - ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream) - : decoder.Decode(Configuration.Default, stream); + ? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default) + : decoder.Decode(Configuration.Default, stream, default); test(imageInfo); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 8b6432ac34..8f3217a58b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(Configuration.Default, memStream)); + Assert.Throws(() => decoder.Decode(Configuration.Default, memStream, default)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 3cc879d6b5..c9b0b3c55e 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); - Image image = decoder.Decode(Configuration.Default, stream); + Image image = decoder.Decode(Configuration.Default, stream, default); PngMetadata metadata = image.Metadata.GetPngMetadata(); Assert.Equal(pngColorType, metadata.ColorType); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 8db1d1aaf2..fd39a828f9 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png input.Save(memoryStream, new PngEncoder()); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); VerifyTextDataIsPresent(meta); @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }); memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream)) + using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) { PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.Contains(meta.TextData, m => m.Equals(expectedText)); @@ -212,7 +212,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new PngDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = decoder.Decode(Configuration.Default, stream, default)) { ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png using (var stream = new MemoryStream(testFile.Bytes, false)) { var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream); + IImageInfo image = decoder.Identify(Configuration.Default, stream, default); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); From 840bf82de6ef82ff77b1e05cc4db89220158ba44 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Wed, 16 Feb 2022 18:00:23 -0800 Subject: [PATCH 06/22] Restore mistakenly deleted logic for resetting stream position --- src/ImageSharp/Image.FromStream.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 0f3203b1fc..5c4e72426e 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -770,6 +770,18 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } + if (stream.CanSeek) + { + if (configuration.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; + } + + // NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that + // would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the + // code below to copy the stream to an in-memory buffer before invoking the action. + } + using var memoryStream = new ChunkedMemoryStream(configuration.MemoryAllocator); await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); memoryStream.Position = 0; From 6ed13c3b41e7e6dfabc9215c8a69e0d9c84f0d85 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Thu, 17 Feb 2022 09:16:06 -0800 Subject: [PATCH 07/22] Fix a bug in LoadAsync(filePath) where the FileStream was being disposed too early --- src/ImageSharp/Image.FromFile.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index fce0835fba..345a5e6c8b 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync( + public static async Task LoadAsync( Configuration configuration, string path, IImageDecoder decoder, @@ -308,7 +308,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(path, nameof(path)); using Stream stream = configuration.FileSystem.OpenRead(path); - return LoadAsync(configuration, stream, decoder, cancellationToken); + return await LoadAsync(configuration, stream, decoder, cancellationToken) + .ConfigureAwait(false); } /// @@ -326,7 +327,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync( + public static async Task> LoadAsync( Configuration configuration, string path, IImageDecoder decoder, @@ -337,7 +338,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(path, nameof(path)); using Stream stream = configuration.FileSystem.OpenRead(path); - return LoadAsync(configuration, stream, decoder, cancellationToken); + return await LoadAsync(configuration, stream, decoder, cancellationToken) + .ConfigureAwait(false); } /// From 2ee0c6e4218e4673a4074f28c11aafe907c9a7f5 Mon Sep 17 00:00:00 2001 From: Dan Kroymann Date: Thu, 17 Feb 2022 09:17:22 -0800 Subject: [PATCH 08/22] Fix unit tests --- .../Formats/Jpg/JpegDecoderTests.cs | 19 ++++--------------- .../Image/ImageTests.ImageLoadTestBase.cs | 4 ++-- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 7a24469597..fa01a5ffcc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -139,27 +139,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.IsType(ex.InnerException); } - [Theory] - [InlineData(0)] - [InlineData(0.5)] - [InlineData(0.9)] - public async Task DecodeAsync_IsCancellable(int percentageOfStreamReadToCancel) + [Fact] + public async Task DecodeAsync_IsCancellable() { var cts = new CancellationTokenSource(); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); pausedStream.OnWaiting(s => { - if (s.Position >= s.Length * percentageOfStreamReadToCancel) - { - cts.Cancel(); - pausedStream.Release(); - } - else - { - // allows this/next wait to unblock - pausedStream.Next(); - } + cts.Cancel(); + pausedStream.Release(); }); var config = Configuration.CreateDefaultInstance(); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index fd743093ef..9992c30550 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests this.localDecoder = detector.As(); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s) => + .Callback((c, s, ct) => { using (var ms = new MemoryStream()) { @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests .Returns(this.localStreamReturnImageRgba32); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s) => + .Callback((c, s, ct) => { using (var ms = new MemoryStream()) { From 2474aab550962ffe59e2c2ef3a44716f8a8e9f23 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 22 Feb 2022 13:44:28 +1100 Subject: [PATCH 09/22] Cleanup and normalization --- src/ImageSharp/Formats/Gif/GifDecoder.cs | 5 +---- src/ImageSharp/Formats/ImageDecoderUtilities.cs | 8 ++++---- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 5 +++-- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 6 ++++-- src/ImageSharp/Image.Decode.cs | 4 ++-- src/ImageSharp/Image.FromStream.cs | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 9d4c882134..6d6cfc0792 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -42,9 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Gif Guard.NotNull(stream, nameof(stream)); var decoder = new GifDecoderCore(configuration, this); - - using var bufferedStream = new BufferedReadStream(configuration, stream); - return decoder.Identify(bufferedStream, cancellationToken); + return decoder.Identify(configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 69b2dd9300..71ecda8938 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken) { using var bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats this IImageDecoderInternals decoder, Configuration configuration, Stream stream, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats Configuration configuration, Stream stream, Func largeImageExceptionFactory, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -61,6 +61,6 @@ namespace SixLabors.ImageSharp.Formats private static InvalidImageContentException DefaultLargeImageExceptionFactory( InvalidMemoryOperationException memoryOperationException, Size dimensions) => - new InvalidImageContentException(dimensions, memoryOperationException); + new(dimensions, memoryOperationException); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 64140f5aa3..823583a900 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -21,14 +21,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - Guard.NotNull(stream, "stream"); + Guard.NotNull(stream, nameof(stream)); var decoder = new TiffDecoderCore(configuration, this); return decoder.Decode(configuration, stream, cancellationToken); } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index e076805e41..af14c31f81 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -38,6 +38,10 @@ namespace SixLabors.ImageSharp.Formats.Webp } } + /// + public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.Decode(configuration, stream, cancellationToken); + /// public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { @@ -46,7 +50,5 @@ namespace SixLabors.ImageSharp.Formats.Webp return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 0aa3031078..3c2b6d67f4 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp /// The image might be filled with memory garbage. /// /// The pixel type - /// The + /// The /// The width of the image /// The height of the image /// The @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); - if (!(decoder is IImageInfoDetector detector)) + if (decoder is not IImageInfoDetector detector) { return (null, null); } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 5c4e72426e..e10d7fe3d9 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -432,9 +432,9 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => await LoadWithFormatAsync(Configuration.Default, stream, cancellationToken).ConfigureAwait(false); + => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. From 95cdd1cef5e23581c7a4733e338372260e5e9e2b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 22 Feb 2022 14:03:43 +1100 Subject: [PATCH 10/22] Fix build --- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 1 - tests/ImageSharp.Benchmarks/Config.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index af14c31f81..c9470a66f2 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -49,6 +49,5 @@ namespace SixLabors.ImageSharp.Formats.Webp return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); } - } } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 2997848211..60d0e76613 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Benchmarks public MultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472), Job.Default.WithRuntime(CoreRuntime.Core31), - Job.Default.WithRuntime(CoreRuntime.Core50).With(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); + Job.Default.WithRuntime(CoreRuntime.Core50).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); } public class ShortMultiFramework : Config @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks public ShortMultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core50).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).With(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); + Job.Default.WithRuntime(CoreRuntime.Core50).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); } public class ShortCore31 : Config From fd4e3a51081309dae438e20f3ebbbdb56ee6f5be Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 25 Feb 2022 00:44:06 +1100 Subject: [PATCH 11/22] Scale allocated chunk sizes to match allocations --- src/ImageSharp/IO/ChunkedMemoryStream.cs | 68 ++++++++++++------ .../IO/ChunkedMemoryStreamTests.cs | 70 ++++++++----------- 2 files changed, 77 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index e39930defe..9c242b5585 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -17,9 +17,19 @@ namespace SixLabors.ImageSharp.IO internal sealed class ChunkedMemoryStream : Stream { /// - /// The default length in bytes of each buffer chunk. + /// The default length in bytes of each buffer chunk when allocating large buffers. /// - public const int DefaultBufferLength = 128 * 1024; + public const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb + + /// + /// The threshold at which to switch to using the large buffer. + /// + public const int DefaultLargeChunkThreshold = DefaultLargeChunkSize / 4; // 1 Mb + + /// + /// The default length in bytes of each buffer chunk when allocating small buffers. + /// + public const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb // The memory allocator. private readonly MemoryAllocator allocator; @@ -27,8 +37,18 @@ namespace SixLabors.ImageSharp.IO // Data private MemoryChunk memoryChunk; - // The length of each buffer chunk - private readonly int chunkLength; + // The length, in bytes, of each large buffer chunk + private readonly int largeChunkSize; + + // The length, in bytes of the total allocation threshold that triggers switching from + // small to large buffer chunks. + private readonly int largeChunkThreshold; + + // The current length, in bytes, of each buffer chunk + private int chunkSize; + + // The total allocation length, in bytes + private int totalAllocation; // Has the stream been disposed. private bool isDisposed; @@ -49,21 +69,15 @@ namespace SixLabors.ImageSharp.IO /// Initializes a new instance of the class. /// public ChunkedMemoryStream(MemoryAllocator allocator) - : this(DefaultBufferLength, allocator) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The length, in bytes of each buffer chunk. - /// The memory allocator. - public ChunkedMemoryStream(int bufferLength, MemoryAllocator allocator) { - Guard.MustBeGreaterThan(bufferLength, 0, nameof(bufferLength)); Guard.NotNull(allocator, nameof(allocator)); - this.chunkLength = bufferLength; + // Tweak our buffer sizes to take the minimum of the provided buffer sizes + // or values scaled from the allocator buffer capacity which provides us with the largest + // available contiguous buffer size. + this.largeChunkSize = Math.Min(DefaultLargeChunkSize, allocator.GetBufferCapacityInBytes()); + this.largeChunkThreshold = Math.Min(DefaultLargeChunkThreshold, this.largeChunkSize / 4); + this.chunkSize = Math.Min(DefaultSmallChunkSize, this.largeChunkSize / 32); this.allocator = allocator; } @@ -191,6 +205,9 @@ namespace SixLabors.ImageSharp.IO case SeekOrigin.End: this.Position = this.Length + offset; break; + default: + ThrowInvalidSeek(); + break; } return this.Position; @@ -219,6 +236,7 @@ namespace SixLabors.ImageSharp.IO this.memoryChunk = null; this.writeChunk = null; this.readChunk = null; + this.totalAllocation = 0; } finally { @@ -519,17 +537,25 @@ namespace SixLabors.ImageSharp.IO } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() - => throw new ObjectDisposedException(null, "The stream is closed."); + private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed."); [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRange(string value) - => throw new ArgumentOutOfRangeException(value); + private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin."); [MethodImpl(MethodImplOptions.AggressiveInlining)] private MemoryChunk AllocateMemoryChunk() { - IMemoryOwner buffer = this.allocator.Allocate(this.chunkLength); + if (this.totalAllocation >= this.largeChunkThreshold) + { + this.chunkSize = this.largeChunkSize; + } + + IMemoryOwner buffer = this.allocator.Allocate(this.chunkSize); + this.totalAllocation += this.chunkSize; + return new MemoryChunk { Buffer = buffer, diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 00a178c8fd..455730e714 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -20,17 +20,7 @@ namespace SixLabors.ImageSharp.Tests.IO { private readonly MemoryAllocator allocator; - public ChunkedMemoryStreamTests() - { - this.allocator = Configuration.Default.MemoryAllocator; - } - - [Fact] - public void MemoryStream_Ctor_InvalidCapacities() - { - Assert.Throws(() => new ChunkedMemoryStream(int.MinValue, this.allocator)); - Assert.Throws(() => new ChunkedMemoryStream(0, this.allocator)); - } + public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; [Fact] public void MemoryStream_GetPositionTest_Negative() @@ -61,11 +51,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -73,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); + byte[] expected = ms.ToArray(); for (int i = 0; i < expected.Length; i++) { @@ -82,11 +72,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -94,8 +84,8 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); - var buffer = new byte[2]; + byte[] expected = ms.ToArray(); + byte[] buffer = new byte[2]; for (int i = 0; i < expected.Length; i += 2) { cms.Read(buffer); @@ -105,11 +95,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultBufferLength)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferSpanTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -117,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.IO ms.CopyTo(cms); cms.Position = 0; - var expected = ms.ToArray(); + byte[] expected = ms.ToArray(); Span buffer = new byte[2]; for (int i = 0; i < expected.Length; i += 2) { @@ -257,24 +247,24 @@ namespace SixLabors.ImageSharp.Tests.IO public void MemoryStream_CopyTo_Invalid() { ChunkedMemoryStream memoryStream; - const string BufferSize = "bufferSize"; + const string bufferSize = nameof(bufferSize); using (memoryStream = new ChunkedMemoryStream(this.allocator)) { - const string Destination = "destination"; - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null)); + const string destination = nameof(destination); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null)); // Validate the destination parameter first. - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); - Assert.Throws(Destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); // Then bufferSize. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); } // After the Stream is disposed, we should fail on all CopyTos. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. - Assert.Throws(BufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); ChunkedMemoryStream disposedStream = memoryStream; @@ -369,7 +359,7 @@ namespace SixLabors.ImageSharp.Tests.IO private MemoryStream CreateTestStream(int length) { - var buffer = new byte[length]; + byte[] buffer = new byte[length]; var random = new Random(); random.NextBytes(buffer); From e6ab9833745e3e75a2ea92e97178ae7272d229b1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 Mar 2022 01:35:11 +1100 Subject: [PATCH 12/22] Ensure pad color is never overwritten --- .../Transforms/Resize/ResizeProcessor{TPixel}.cs | 3 +++ .../Processors/Transforms/Resize/ResizeWorker.cs | 16 ++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index c0bf9291e0..e0ccb487f7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -61,6 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle destinationRectangle = this.destinationRectangle; bool compand = this.options.Compand; bool premultiplyAlpha = this.options.PremultiplyAlpha; + + this.options.PadColor = Color.GreenYellow; + bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) && this.options.PadColor != default; TPixel fillColor = this.options.PadColor.ToPixel(); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 90cbf8bda0..5f3c609898 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -80,19 +80,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, - destWidth, + targetWorkingRect.Width, workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( this.workerHeight, - destWidth, + targetWorkingRect.Width, preferContiguosImageBuffers: true, options: AllocationOptions.Clean); this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); - this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(destWidth); + this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(targetWorkingRect.Width); this.currentWindow = new RowInterval(0, this.workerHeight); } @@ -118,6 +118,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // When creating transposedFirstPassBuffer, we made sure it's contiguous: Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; + int width = this.targetWorkingRect.Width; for (int y = rowInterval.Min; y < rowInterval.Max; y++) { // Ensure offsets are normalized for cropping and padding. @@ -131,17 +134,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); int top = kernel.StartIndex - this.currentWindow.Min; + ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; - for (int x = 0; x < this.destWidth; x++) + for (int x = left; x < right; x++) { ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); + Unsafe.Add(ref tempRowBase, x - left) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.DangerousGetRowSpan(y); + Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } From baa8c7b8e76a627352d50a23e1b1a41682c7fb23 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 Mar 2022 02:06:47 +1100 Subject: [PATCH 13/22] Fix pad memory access error --- .../Processing/Extensions/Transforms/PadExtensions.cs | 6 +++++- .../Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs | 5 +---- .../Processing/Processors/Transforms/Resize/ResizeWorker.cs | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs index ff374a9ac4..5e9e420321 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Processing { /// @@ -29,9 +31,11 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) { + Size size = source.GetCurrentSize(); var options = new ResizeOptions { - Size = new Size(width, height), + // Prevent downsizing. + Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)), Mode = ResizeMode.BoxPad, Sampler = KnownResamplers.NearestNeighbor, PadColor = color diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index e0ccb487f7..af8adc2d5e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -61,12 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle destinationRectangle = this.destinationRectangle; bool compand = this.options.Compand; bool premultiplyAlpha = this.options.PremultiplyAlpha; - - this.options.PadColor = Color.GreenYellow; - + TPixel fillColor = this.options.PadColor.ToPixel(); bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) && this.options.PadColor != default; - TPixel fillColor = this.options.PadColor.ToPixel(); // Handle resize dimensions identical to the original if (source.Width == destination.Width diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 5f3c609898..a4a504317e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -80,14 +80,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, - targetWorkingRect.Width, + destWidth, workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( this.workerHeight, - targetWorkingRect.Width, + destWidth, preferContiguosImageBuffers: true, options: AllocationOptions.Clean); From 06bdf138bd53cb59f9266621eb42be5c20d39e83 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 Mar 2022 02:29:22 +1100 Subject: [PATCH 14/22] Optimize memory usage for resize pad operations --- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Resize/ResizeProcessor{TPixel}.cs | 13 +++++-------- .../Transforms/Resize/ResizeWorker.cs | 18 ++++++++---------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index c9dda5f6bc..f4bda8bf4f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(nint destIdx) => ref this.kernels[destIdx]; /// /// Computes the weights to apply at each pixel when resizing. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index af8adc2d5e..b346758963 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -209,21 +209,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // To reintroduce parallel processing, we would launch multiple workers // for different row intervals of the image. - using (var worker = new ResizeWorker( + using var worker = new ResizeWorker( configuration, sourceRegion, conversionModifiers, horizontalKernelMap, verticalKernelMap, - destination.Width, interest, - destinationRectangle.Location)) - { - worker.Initialize(); + destinationRectangle.Location); + worker.Initialize(); - var workingInterval = new RowInterval(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); - } + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); } private readonly struct NNRowOperation : IRowOperation diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index a4a504317e..2a582ed8ab 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -39,8 +39,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernelMap verticalKernelMap; - private readonly int destWidth; - private readonly Rectangle targetWorkingRect; private readonly Point targetOrigin; @@ -57,7 +55,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms PixelConversionModifiers conversionModifiers, ResizeKernelMap horizontalKernelMap, ResizeKernelMap verticalKernelMap, - int destWidth, Rectangle targetWorkingRect, Point targetOrigin) { @@ -67,7 +64,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.conversionModifiers = conversionModifiers; this.horizontalKernelMap = horizontalKernelMap; this.verticalKernelMap = verticalKernelMap; - this.destWidth = destWidth; this.targetWorkingRect = targetWorkingRect; this.targetOrigin = targetOrigin; @@ -80,14 +76,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, - destWidth, + targetWorkingRect.Width, workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( this.workerHeight, - destWidth, + targetWorkingRect.Width, preferContiguosImageBuffers: true, options: AllocationOptions.Clean); @@ -137,9 +133,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; - for (int x = left; x < right; x++) + for (nint x = left; x < right; x++) { - ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); + ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, (x - left) * this.workerHeight); // Destination color components Unsafe.Add(ref tempRowBase, x - left) = kernel.ConvolveCore(ref firstPassColumnBase); @@ -174,6 +170,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Span tempRowSpan = this.tempRowBuffer.GetSpan(); Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); @@ -188,13 +186,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - for (int x = this.targetWorkingRect.Left; x < this.targetWorkingRect.Right; x++) + for (nint x = left; x < right; x++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); // optimization for: // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, (x - left) * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } From 0197de6f6c289c068d0b614a5254fe655ac34aee Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 Mar 2022 02:38:50 +1100 Subject: [PATCH 15/22] Update tests --- .../Processing/Processors/Transforms/ResizeTests.cs | 6 ++++-- .../ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png | 4 ++-- .../ResizeTests/ResizeWithPadMode_CalliphoraPartial.png | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 6e275b824e..b1ff3df08c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -472,7 +472,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad + Mode = ResizeMode.BoxPad, + PadColor = Color.HotPink }; image.Mutate(x => x.Resize(options)); @@ -580,7 +581,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad + Mode = ResizeMode.Pad, + PadColor = Color.Lavender }; image.Mutate(x => x.Resize(options)); diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png index 35d633c86f..804c5dcf9a 100644 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithBoxPadMode_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1642520a9d4491f55af5a83280423929529e3d528637538d148b9dc2ecdd6d2d -size 365839 +oid sha256:77086032bab11b91c68bc1686179063edbadc9d453574e4f087b2bbd677b4c8e +size 402367 diff --git a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png index 690c2ad46c..bfa048f82c 100644 --- a/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png +++ b/tests/Images/External/ReferenceOutput/ResizeTests/ResizeWithPadMode_CalliphoraPartial.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:060826324dcd4baa1df1b075707a9bd9527cf87c1d156e3beb3d8abb499bdcd5 -size 361829 +oid sha256:5c0653aa2b726574fbea4cc308c269ff5e534d38bb48c0e77470c11042a395fd +size 400267 From 555a0d7b67c952e2884b33b685a3f6f21745e618 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 10 Mar 2022 15:25:08 +0100 Subject: [PATCH 16/22] Fix app1 parsing --- .../Formats/Jpeg/JpegDecoderCore.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 023928f37d..ef4e3ffac2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Processes the App1 marker retrieving any stored metadata + /// Processes the App1 marker retrieving any stored metadata. /// /// The input stream. /// The remaining bytes in the segment block. @@ -687,7 +687,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int XmpMarkerLength = 29; if (remaining < ExifMarkerLength || this.IgnoreMetadata) { - // Skip the application header length + // Skip the application header length. stream.Skip(remaining); return; } @@ -697,12 +697,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - // XMP marker is the longest, so read at least that many bytes into temp. + // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes. stream.Read(this.temp, 0, ExifMarkerLength); + remaining -= ExifMarkerLength; if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { - remaining -= ExifMarkerLength; this.hasExif = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); @@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers + // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers. this.ExtendProfile(ref this.exifData, profile); } @@ -722,9 +722,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { - stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength); - remaining -= XmpMarkerLength; - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength))) + int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; + stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes); + remaining -= remainingXmpMarkerBytes; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) { this.hasXmp = true; byte[] profile = new byte[remaining]; @@ -736,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers. this.ExtendProfile(ref this.xmpData, profile); } From 29aa049b714d7c7fe0df90736b0c3e80d4d8c25a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 10 Mar 2022 15:31:28 +0100 Subject: [PATCH 17/22] Add test for #2057 --- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 1 - .../Formats/Jpg/JpegDecoderTests.cs | 17 +++++++++++++++-- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Jpg/issues/Issue2057-App1Parsing.jpg | 3 +++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 840cc9f685..ffd58e5b73 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Runtime.CompilerServices; -using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 7a24469597..4409f91a02 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -21,7 +21,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes - [Trait("Format", "Jpg")] + [Trait("Format", "Jpg")] public partial class JpegDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } - private static JpegDecoder JpegDecoder => new JpegDecoder(); + private static JpegDecoder JpegDecoder => new(); [Fact] public void ParseStream_BasicPropertiesAreCorrect() @@ -213,6 +213,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + // https://github.com/SixLabors/ImageSharp/issues/2057 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)] + public void Issue2057_DecodeWorks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(JpegDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + } + // 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/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 5ed0a12f7d..2886b5106d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -262,6 +262,7 @@ namespace SixLabors.ImageSharp.Tests public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; + public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg b/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg new file mode 100644 index 0000000000..d1ffe4ac91 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue2057-App1Parsing.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7048dee15946bf981e5b0d2481ffcb8a64684fddca07172275b13a05f01b6b63 +size 1631109 From faea7d05dbd14392c7d9e8136876461793d42578 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 13 Mar 2022 23:42:42 +0100 Subject: [PATCH 18/22] AsyncImageSharp option for load testing --- .../LoadResizeSaveStressRunner.cs | 48 +++++++++++++++++++ .../LoadResizeSaveParallelMemoryStress.cs | 24 ++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index eda054968e..81a95cd1ee 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; @@ -9,6 +10,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; @@ -124,6 +126,32 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, action); + public Task ForEachImageParallelAsync(Func action) + { + int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0 + ? this.MaxDegreeOfParallelism + : Environment.ProcessorCount; + int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism); + + List tasks = new(); + for (int i = 0; i < this.Images.Length; i += partitionSize) + { + int end = Math.Min(i + partitionSize, this.Images.Length); + Task task = RunPartition(i, end); + tasks.Add(task); + } + + return Task.WhenAll(tasks); + + Task RunPartition(int start, int end) => Task.Run(async () => + { + for (int i = start; i < end; i++) + { + await action(this.Images[i]); + } + }); + } + private void LogImageProcessed(int width, int height) { this.LastProcessedImageSize = new Size(width, height); @@ -197,6 +225,26 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave image.Save(output, this.imageSharpJpegEncoder); } + public async Task ImageSharpResizeAsync(string input) + { + using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); + // Resize it to fit a 150x150 square + using var image = await ImageSharpImage.LoadAsync(input); + this.LogImageProcessed(image.Width, image.Height); + + image.Mutate(i => i.Resize(new ResizeOptions + { + Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize), + Mode = ResizeMode.Max + })); + + // Reduce the size of the file + image.Metadata.ExifProfile = null; + + // Save the results + await image.SaveAsync(output, this.imageSharpJpegEncoder); + } + public void MagickResize(string input) { using var image = new MagickImage(input); diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index c7484daa0d..95e64b1539 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); Stopwatch timer; - if (options == null || !options.ImageSharp) + if (options == null || !(options.ImageSharp || options.AsyncImageSharp)) { RunBenchmarkSwitcher(lrs, out timer); } @@ -74,7 +74,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { for (int i = 0; i < options.RepeatCount; i++) { - lrs.ImageSharpBenchmarkParallel(); + if (options.AsyncImageSharp) + { + lrs.ImageSharpBenchmarkParallelAsync(); + } + else + { + lrs.ImageSharpBenchmarkParallel(); + } + + Console.WriteLine("OK"); } } catch (Exception ex) @@ -221,6 +230,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private class CommandLineOptions { + [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] + public bool AsyncImageSharp { get; set; } + [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] public bool ImageSharp { get; set; } @@ -277,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; public MemoryAllocator CreateMemoryAllocator() { @@ -330,11 +342,17 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } }); + private void ImageSharpBenchmarkParallelAsync() => + this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f)) + .GetAwaiter() + .GetResult(); + private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); + private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); From ebea485f96f96debbc883fffa1b4ded20581cc10 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 14 Mar 2022 19:50:39 +1100 Subject: [PATCH 19/22] Update ResizeWorker.cs --- .../Processors/Transforms/Resize/ResizeWorker.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 2a582ed8ab..5e7de18037 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -133,12 +133,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; - for (nint x = left; x < right; x++) + for (nint x = 0; x < (right - left); x++) { - ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, (x - left) * this.workerHeight); + ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); // Destination color components - Unsafe.Add(ref tempRowBase, x - left) = kernel.ConvolveCore(ref firstPassColumnBase); + Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); @@ -172,6 +172,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int left = this.targetWorkingRect.Left; int right = this.targetWorkingRect.Right; + int targetOriginX = left > this.targetOrigin.X ? left - this.targetOrigin.X : this.targetOrigin.X; for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); @@ -186,13 +187,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - for (nint x = left; x < right; x++) + for (nint x = 0; x < (right - left); x++) { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - this.targetOrigin.X); + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); // optimization for: // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, (x - left) * this.workerHeight) = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } From 1ad9e561377a91fc2568bae4def4193f54aecae8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 14 Mar 2022 20:49:39 +1100 Subject: [PATCH 20/22] Use graduated buffers --- src/ImageSharp/IO/ChunkedMemoryStream.cs | 60 +++++++------------ .../IO/ChunkedMemoryStreamTests.cs | 40 ++++++++----- 2 files changed, 45 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index 9c242b5585..c34ed9878e 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -16,39 +16,17 @@ namespace SixLabors.ImageSharp.IO /// internal sealed class ChunkedMemoryStream : Stream { - /// - /// The default length in bytes of each buffer chunk when allocating large buffers. - /// - public const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb - - /// - /// The threshold at which to switch to using the large buffer. - /// - public const int DefaultLargeChunkThreshold = DefaultLargeChunkSize / 4; // 1 Mb - - /// - /// The default length in bytes of each buffer chunk when allocating small buffers. - /// - public const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb - // The memory allocator. private readonly MemoryAllocator allocator; // Data private MemoryChunk memoryChunk; - // The length, in bytes, of each large buffer chunk - private readonly int largeChunkSize; - - // The length, in bytes of the total allocation threshold that triggers switching from - // small to large buffer chunks. - private readonly int largeChunkThreshold; - - // The current length, in bytes, of each buffer chunk - private int chunkSize; + // The total number of allocated chunks + private int chunkCount; - // The total allocation length, in bytes - private int totalAllocation; + // The length of the largest contiguous buffer that can be handled by the allocator. + private readonly int allocatorCapacity; // Has the stream been disposed. private bool isDisposed; @@ -72,12 +50,7 @@ namespace SixLabors.ImageSharp.IO { Guard.NotNull(allocator, nameof(allocator)); - // Tweak our buffer sizes to take the minimum of the provided buffer sizes - // or values scaled from the allocator buffer capacity which provides us with the largest - // available contiguous buffer size. - this.largeChunkSize = Math.Min(DefaultLargeChunkSize, allocator.GetBufferCapacityInBytes()); - this.largeChunkThreshold = Math.Min(DefaultLargeChunkThreshold, this.largeChunkSize / 4); - this.chunkSize = Math.Min(DefaultSmallChunkSize, this.largeChunkSize / 32); + this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); this.allocator = allocator; } @@ -236,7 +209,7 @@ namespace SixLabors.ImageSharp.IO this.memoryChunk = null; this.writeChunk = null; this.readChunk = null; - this.totalAllocation = 0; + this.chunkCount = 0; } finally { @@ -548,13 +521,10 @@ namespace SixLabors.ImageSharp.IO [MethodImpl(MethodImplOptions.AggressiveInlining)] private MemoryChunk AllocateMemoryChunk() { - if (this.totalAllocation >= this.largeChunkThreshold) - { - this.chunkSize = this.largeChunkSize; - } - - IMemoryOwner buffer = this.allocator.Allocate(this.chunkSize); - this.totalAllocation += this.chunkSize; + // Tweak our buffer sizes to take the minimum of the provided buffer sizes + // or the allocator buffer capacity which provides us with the largest + // available contiguous buffer size. + IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); return new MemoryChunk { @@ -573,6 +543,16 @@ namespace SixLabors.ImageSharp.IO } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetChunkSize(int i) + { +#pragma warning disable IDE1006 // Naming Styles + const int _128K = 1 << 17; + const int _4M = 1 << 22; + return i < 16 ? _128K * (1 << (i / 4)) : _4M; +#pragma warning restore IDE1006 // Naming Styles + } + private sealed class MemoryChunk : IDisposable { private bool isDisposed; diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 455730e714..6bcb7e7ad3 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -18,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests.IO /// public class ChunkedMemoryStreamTests { + /// + /// The default length in bytes of each buffer chunk when allocating large buffers. + /// + private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb + + /// + /// The default length in bytes of each buffer chunk when allocating small buffers. + /// + private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb + private readonly MemoryAllocator allocator; public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; @@ -51,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -72,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -95,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferSpanTest(int length) { using MemoryStream ms = this.CreateTestStream(length); From afe9505b291717e2fdef428c604bd37a4a771dd1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 14 Mar 2022 20:59:59 +1100 Subject: [PATCH 21/22] Fix offset --- .../Processing/Processors/Transforms/Resize/ResizeWorker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 5e7de18037..45f35b7d90 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int left = this.targetWorkingRect.Left; int right = this.targetWorkingRect.Right; - int targetOriginX = left > this.targetOrigin.X ? left - this.targetOrigin.X : this.targetOrigin.X; + int targetOriginX = this.targetOrigin.X; for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); @@ -187,13 +187,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - for (nint x = 0; x < (right - left); x++) + for (nint x = left, z = 0; x < right; x++, z++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); // optimization for: // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, x * this.workerHeight) = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } From e554dcaa73d2b17c7efc2246bbfa12bc39f3ba9f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 15 Mar 2022 01:27:47 +0000 Subject: [PATCH 22/22] Update src/ImageSharp/IO/ChunkedMemoryStream.cs Co-authored-by: Anton Firszov --- src/ImageSharp/IO/ChunkedMemoryStream.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index c34ed9878e..a1ea2b7600 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -546,6 +546,8 @@ namespace SixLabors.ImageSharp.IO [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetChunkSize(int i) { + // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator. + // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 #pragma warning disable IDE1006 // Naming Styles const int _128K = 1 << 17; const int _4M = 1 << 22;