From dfe04e36e3349e2cef9e6548d889c732e9d817ce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 22:12:13 +1000 Subject: [PATCH] Refactor Bmp and Gif --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 40 ++++------ src/ImageSharp/Formats/Bmp/BmpDecoder2.cs | 30 -------- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 41 +++++------ .../Formats/Bmp/BmpDecoderOptions.cs | 2 +- .../Formats/Bmp/IBmpDecoderOptions.cs | 16 ---- src/ImageSharp/Formats/DecoderOptions.cs | 6 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 35 ++++----- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 73 ++++++++++--------- .../Formats/Gif/GifDecoderOptions.cs | 14 ++++ ...ernals.cs => IImageDecoderInternals{T}.cs} | 10 ++- .../Formats/ImageDecoderUtilities.cs | 17 +++-- 11 files changed, 119 insertions(+), 165 deletions(-) delete mode 100644 src/ImageSharp/Formats/Bmp/BmpDecoder2.cs delete mode 100644 src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Gif/GifDecoderOptions.cs rename src/ImageSharp/Formats/{IImageDecoderInternals.cs => IImageDecoderInternals{T}.cs} (87%) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index e76448938..326ba0b74 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -10,43 +10,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image decoder for generating an image out of a Windows bitmap stream. /// - /// - /// Does not support the following formats at the moment: - /// - /// JPG - /// PNG - /// Some OS/2 specific subtypes like: Bitmap Array, Color Icon, Color Pointer, Icon, Pointer. - /// - /// Formats will be supported in a later releases. We advise always - /// to use only 24 Bit Windows bitmaps. - /// - public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDetector + public class BmpDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. - /// - public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + BmpDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); + return new BmpDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs deleted file mode 100644 index 97439182d..000000000 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; -using System.Threading; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Bmp -{ - /// - /// Image decoder for generating an image out of a Windows bitmap stream. - /// - public class BmpDecoder2 : ImageDecoder - { - /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => throw new NotImplementedException(); - } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 26687ff16..f00d4cd03 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// A useful decoding source example can be found at /// - internal sealed class BmpDecoderCore : IImageDecoderInternals + internal sealed class BmpDecoderCore : IImageDecoderInternals { /// /// The default mask for the red part of the color for 16 bit rgb bitmaps. @@ -90,33 +90,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp private BmpInfoHeader infoHeader; /// - /// Used for allocating memory during processing operations. + /// The global configuration. /// - private readonly MemoryAllocator memoryAllocator; + private readonly Configuration configuration; /// - /// The bitmap decoder options. + /// Used for allocating memory during processing operations. /// - private readonly IBmpDecoderOptions options; + private readonly MemoryAllocator memoryAllocator; /// /// Initializes a new instance of the class. /// - /// The configuration. /// The options. - public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) + public BmpDecoderCore(BmpDecoderOptions options) { - this.Configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.Options = options; } /// - public Configuration Configuration { get; } + public BmpDecoderOptions Options { get; } - /// - /// Gets the dimensions of the image. - /// + /// public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); /// @@ -128,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); + image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -325,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte colorIdx = bufferRow[x]; if (undefinedPixelsSpan[rowStartIdx + x]) { - switch (this.options.RleSkippedPixelHandling) + switch (this.Options.RleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); @@ -397,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int idx = rowStartIdx + (x * 3); if (undefinedPixelsSpan[yMulWidth + x]) { - switch (this.options.RleSkippedPixelHandling) + switch (this.Options.RleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); @@ -943,7 +940,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -971,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -1006,7 +1003,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(rowSpan); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, bgraRowSpan, width); @@ -1042,7 +1039,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -1056,7 +1053,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, bgraRowSpan, width); diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index 74509d68f..535f819d2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Image decoder options for decoding Windows bitmap streams. + /// Configuration options for decoding Windows Bitmap images. /// public sealed class BmpDecoderOptions : ISpecializedDecoderOptions { diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs deleted file mode 100644 index ff88d15a3..000000000 --- a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Bmp -{ - /// - /// Image decoder options for decoding Windows bitmap streams. - /// - internal interface IBmpDecoderOptions - { - /// - /// Gets the value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. - /// - RleSkippedPixelHandling RleSkippedPixelHandling { get; } - } -} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index a690b0607..6b35e2614 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -24,10 +24,8 @@ namespace SixLabors.ImageSharp.Formats public bool SkipMetadata { get; set; } = false; /// - /// Gets or sets the number of image frames to decode. - /// Leave to decode all frames. + /// Gets or sets the maximum number of image frames to decode, inclusive. /// - // supported decoders may handle this internally but we will fallback to removeing additional frames after decode. - public int? MaxFrames { get; set; } = null; + public int MaxFrames { get; set; } = int.MaxValue; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 6d6cfc079..5550c419e 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.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif @@ -11,37 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Decoder for generating an image out of a gif encoded stream. /// - public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDetector + public sealed class GifDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } = false; - - /// - /// Gets or sets the decoding mode for multi-frame images - /// - public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + GifDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new GifDecoderCore(configuration, this); - return decoder.Identify(configuration, stream, cancellationToken); + return new GifDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 2932cafe2..c63a3e08e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Performs the gif decoding operation. /// - internal sealed class GifDecoderCore : IImageDecoderInternals + internal sealed class GifDecoderCore : IImageDecoderInternals { /// /// The temp buffer used to reduce allocations. @@ -56,6 +56,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private GifImageDescriptor imageDescriptor; + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The maximum number of frames to decode. + /// + private readonly int maxFrames; + + /// + /// Whether to skip metadata during decode. + /// + private readonly bool skipMetadata; + /// /// The abstract metadata. /// @@ -69,39 +89,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// - /// The configuration. /// The decoder options. - public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) + public GifDecoderCore(GifDecoderOptions options) { - this.IgnoreMetadata = options.IgnoreMetadata; - this.DecodingMode = options.DecodingMode; - this.Configuration = configuration ?? Configuration.Default; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.maxFrames = options.GeneralOptions.MaxFrames; } /// - public Configuration Configuration { get; } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; internal set; } + public GifDecoderOptions Options { get; } - /// - /// Gets the decoding mode for multi-frame images. - /// - public FrameDecodingMode DecodingMode { get; } - - /// - /// Gets the dimensions of the image. - /// + /// public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); - private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; - /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + int frameCount = 0; Image image = null; ImageFrame previousFrame = null; try @@ -114,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (nextFlag == GifConstants.ImageLabel) { - if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First) + if (previousFrame != null && frameCount++ <= this.maxFrames) { break; } @@ -277,9 +284,9 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); - if (isXmp && !this.IgnoreMetadata) + if (isXmp && !this.skipMetadata) { - var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator); + var extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator); if (extension.Data.Length > 0) { this.metadata.XmpProfile = new XmpProfile(extension.Data); @@ -346,13 +353,13 @@ namespace SixLabors.ImageSharp.Formats.Gif GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); } - if (this.IgnoreMetadata) + if (this.skipMetadata) { this.stream.Seek(length, SeekOrigin.Current); continue; } - using IMemoryOwner commentsBuffer = this.MemoryAllocator.Allocate(length); + using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); Span commentsSpan = commentsBuffer.GetSpan(); this.stream.Read(commentsSpan); @@ -385,11 +392,11 @@ namespace SixLabors.ImageSharp.Formats.Gif if (this.imageDescriptor.LocalColorTableFlag) { int length = this.imageDescriptor.LocalColorTableSize * 3; - localColorTable = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + localColorTable = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); this.stream.Read(localColorTable.GetSpan()); } - indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); + indices = this.configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); this.ReadFrameIndices(indices); Span rawColorTable = default; @@ -423,7 +430,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void ReadFrameIndices(Buffer2D indices) { int minCodeSize = this.stream.ReadByte(); - using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream); + using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); lzwDecoder.DecodePixels(minCodeSize, indices); } @@ -451,12 +458,12 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (!transFlag) { - image = new Image(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); + image = new Image(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); } else { // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata); + image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); } this.SetFrameMetadata(image.Frames.RootFrame.Metadata); @@ -675,7 +682,7 @@ namespace SixLabors.ImageSharp.Formats.Gif if (globalColorTableLength > 0) { - this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); + this.globalColorTable = this.memoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); // Read the global color table data from the stream stream.Read(this.globalColorTable.GetSpan()); diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs new file mode 100644 index 000000000..429c1fee1 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Configuration options for decoding Gif images. + /// + public sealed class GifDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs similarity index 87% rename from src/ImageSharp/Formats/IImageDecoderInternals.cs rename to src/ImageSharp/Formats/IImageDecoderInternals{T}.cs index e190f7add..ffc839d34 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs @@ -9,14 +9,16 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats { /// - /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// Abstraction for shared internals for XXXDecoderCore implementations to be used with . /// - internal interface IImageDecoderInternals + /// The type of specialized decoder options. + internal interface IImageDecoderInternals + where T : ISpecializedDecoderOptions { /// - /// Gets the associated configuration. + /// Gets the specialized decoder options. /// - Configuration Configuration { get; } + T Options { get; } /// /// Gets the dimensions of the image being decoded. diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 71ecda893..fc78f14b3 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -12,11 +12,12 @@ namespace SixLabors.ImageSharp.Formats { internal static class ImageDecoderUtilities { - public static IImageInfo Identify( - this IImageDecoderInternals decoder, + public static IImageInfo Identify( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions { using var bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -30,20 +31,22 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, Func largeImageExceptionFactory, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream);