Browse Source

Expose ImageInfo and add IImageFormat to metadata

pull/2317/head
James Jackson-South 3 years ago
parent
commit
89d6fc8d69
  1. 2
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 2
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  4. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  5. 6
      src/ImageSharp/Formats/IImageDecoder.cs
  6. 4
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  7. 86
      src/ImageSharp/Formats/ImageDecoder.cs
  8. 2
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  9. 3
      src/ImageSharp/Formats/ImageFormatManager.cs
  10. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  11. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  12. 2
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  13. 2
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  14. 15
      src/ImageSharp/Formats/PixelTypeInfo.cs
  15. 4
      src/ImageSharp/Formats/Png/PngDecoder.cs
  16. 2
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  17. 36
      src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
  18. 2
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  19. 2
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  20. 2
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  21. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  22. 2
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  23. 2
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  24. 2
      src/ImageSharp/IImageInfo.cs
  25. 12
      src/ImageSharp/Image.Decode.cs
  26. 12
      src/ImageSharp/Image.FromBytes.cs
  27. 30
      src/ImageSharp/Image.FromFile.cs
  28. 32
      src/ImageSharp/Image.FromStream.cs
  29. 4
      src/ImageSharp/ImageInfo.cs
  30. 6
      src/ImageSharp/ImageInfoExtensions.cs
  31. 20
      src/ImageSharp/Metadata/ImageMetadata.cs
  32. 2
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs
  33. 2
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
  34. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  35. 4
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  36. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
  37. 4
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  38. 6
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  39. 10
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  40. 6
      tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
  41. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  42. 8
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  43. 2
      tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
  44. 4
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs
  45. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  46. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  47. 2
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  48. 48
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  49. 4
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  50. 10
      tests/ImageSharp.Tests/TestFormat.cs
  51. 7
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  52. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  53. 14
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

2
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -20,7 +20,7 @@ public sealed class BmpDecoder : SpecializedImageDecoder<BmpDecoderOptions>
public static BmpDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -209,7 +209,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);

2
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -20,7 +20,7 @@ public sealed class GifDecoder : ImageDecoder
public static GifDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -175,7 +175,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
try
{

6
src/ImageSharp/Formats/IImageDecoder.cs

@ -15,9 +15,9 @@ public interface IImageDecoder
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <returns>The <see cref="ImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public IImageInfo Identify(DecoderOptions options, Stream stream);
public ImageInfo Identify(DecoderOptions options, Stream stream);
/// <summary>
/// Reads the raw image information from the specified stream.
@ -27,7 +27,7 @@ public interface IImageDecoder
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="Task{IImageInfo}"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
public Task<IImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default);
public Task<ImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default);
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}"/> of a specific pixel type.

4
src/ImageSharp/Formats/IImageDecoderInternals.cs

@ -41,10 +41,10 @@ internal interface IImageDecoderInternals
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/>.</returns>
/// <returns>The <see cref="ImageInfo"/>.</returns>
/// <remarks>
/// Cancellable synchronous method. In case of cancellation,
/// an <see cref="OperationCanceledException"/> shall be thrown which will be handled on the call site.
/// </remarks>
IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken);
}

86
src/ImageSharp/Formats/ImageDecoder.cs

@ -17,44 +17,74 @@ public abstract class ImageDecoder : IImageDecoder
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(
options,
stream,
s => this.Decode<TPixel>(options, s, default));
{
Image<TPixel> image = WithSeekableStream(
options,
stream,
s => this.Decode<TPixel>(options, s, default));
this.SetDecoderFormat(options.Configuration, image);
return image;
}
/// <inheritdoc/>
public Image Decode(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
s => this.Decode(options, s, default));
{
Image image = WithSeekableStream(
options,
stream,
s => this.Decode(options, s, default));
this.SetDecoderFormat(options.Configuration, image);
return image;
}
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableMemoryStreamAsync(
options,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken);
{
Image<TPixel> image = await WithSeekableMemoryStreamAsync(
options,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, image);
return image;
}
/// <inheritdoc/>
public Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> WithSeekableMemoryStreamAsync(
public async Task<Image> DecodeAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
{
Image image = await WithSeekableMemoryStreamAsync(
options,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, image);
return image;
}
/// <inheritdoc/>
public IImageInfo Identify(DecoderOptions options, Stream stream)
=> WithSeekableStream(
options,
stream,
s => this.Identify(options, s, default));
public ImageInfo Identify(DecoderOptions options, Stream stream)
{
ImageInfo info = WithSeekableStream(
options,
stream,
s => this.Identify(options, s, default));
this.SetDecoderFormat(options.Configuration, info);
return info;
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
public Task<ImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> WithSeekableMemoryStreamAsync(
options,
stream,
@ -98,9 +128,9 @@ public abstract class ImageDecoder : IImageDecoder
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns>
/// <returns>The <see cref="ImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
protected abstract ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <summary>
/// Performs a scaling operation against the decoded image. If the target size is not set, or the image size
@ -252,4 +282,10 @@ public abstract class ImageDecoder : IImageDecoder
memoryStream.Position = 0;
return await action(memoryStream, position, cancellationToken).ConfigureAwait(false);
}
internal void SetDecoderFormat(Configuration configuration, Image image)
=> image.Metadata.DecodedImageFormat = configuration.ImageFormatsManager.FindFormatByDecoder(this);
internal void SetDecoderFormat(Configuration configuration, ImageInfo info)
=> info.Metadata.DecodedImageFormat = configuration.ImageFormatsManager.FindFormatByDecoder(this);
}

2
src/ImageSharp/Formats/ImageDecoderUtilities.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats;
/// </summary>
internal static class ImageDecoderUtilities
{
internal static IImageInfo Identify(
internal static ImageInfo Identify(
this IImageDecoderInternals decoder,
Configuration configuration,
Stream stream,

3
src/ImageSharp/Formats/ImageFormatManager.cs

@ -117,6 +117,9 @@ public class ImageFormatManager
public IImageFormat? FindFormatByMimeType(string mimeType)
=> this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
internal IImageFormat? FindFormatByDecoder(IImageDecoder decoder)
=> this.mimeTypeDecoders.FirstOrDefault(x => x.Value == decoder).Key;
/// <summary>
/// Sets a specific image encoder as the encoder for a specific image format.
/// </summary>

2
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -20,7 +20,7 @@ public sealed class JpegDecoder : SpecializedImageDecoder<JpegDecoderOptions>
public static JpegDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -225,7 +225,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.InitExifProfile();

2
src/ImageSharp/Formats/Pbm/PbmDecoder.cs

@ -36,7 +36,7 @@ public sealed class PbmDecoder : ImageDecoder
public static PbmDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -83,7 +83,7 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ProcessHeader(stream);

15
src/ImageSharp/Formats/PixelTypeInfo.cs

@ -4,6 +4,9 @@
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
// TODO: Review this class as it's used to represent 2 different things.
// 1.The encoded image pixel format.
// 2. The pixel format of the decoded image.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
@ -16,9 +19,7 @@ public class PixelTypeInfo
/// </summary>
/// <param name="bitsPerPixel">Color depth, in number of bits per pixel.</param>
public PixelTypeInfo(int bitsPerPixel)
{
this.BitsPerPixel = bitsPerPixel;
}
=> this.BitsPerPixel = bitsPerPixel;
/// <summary>
/// Initializes a new instance of the <see cref="PixelTypeInfo"/> class.
@ -43,12 +44,10 @@ public class PixelTypeInfo
public PixelAlphaRepresentation? AlphaRepresentation { get; }
internal static PixelTypeInfo Create<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> =>
new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
where TPixel : unmanaged, IPixel<TPixel>
=> new(Unsafe.SizeOf<TPixel>() * 8);
internal static PixelTypeInfo Create<TPixel>(PixelAlphaRepresentation alpha)
where TPixel : unmanaged, IPixel<TPixel>
{
return new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8, alpha);
}
=> new(Unsafe.SizeOf<TPixel>() * 8, alpha);
}

4
src/ImageSharp/Formats/Png/PngDecoder.cs

@ -20,7 +20,7 @@ public sealed class PngDecoder : ImageDecoder
public static PngDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));
@ -49,7 +49,7 @@ public sealed class PngDecoder : ImageDecoder
Guard.NotNull(stream, nameof(stream));
PngDecoderCore decoder = new(options, true);
IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken);
ImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken);
stream.Position = 0;
PngMetadata meta = info.Metadata.GetPngMetadata();

2
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -247,7 +247,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
ImageMetadata metadata = new();
PngMetadata pngMetadata = metadata.GetPngMetadata();

36
src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs

@ -17,34 +17,54 @@ public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedIma
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(T options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(
{
Image<TPixel> image = WithSeekableStream(
options.GeneralOptions,
stream,
s => this.Decode<TPixel>(options, s, default));
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image;
}
/// <inheritdoc/>
public Image Decode(T options, Stream stream)
=> WithSeekableStream(
{
Image image = WithSeekableStream(
options.GeneralOptions,
stream,
s => this.Decode(options, s, default));
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image;
}
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(T options, Stream stream, CancellationToken cancellationToken = default)
public async Task<Image<TPixel>> DecodeAsync<TPixel>(T options, Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableMemoryStreamAsync(
{
Image<TPixel> image = await WithSeekableMemoryStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => this.Decode<TPixel>(options, s, ct),
cancellationToken);
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image;
}
/// <inheritdoc/>
public Task<Image> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default)
=> WithSeekableMemoryStreamAsync(
public async Task<Image> DecodeAsync(T options, Stream stream, CancellationToken cancellationToken = default)
{
Image image = await WithSeekableMemoryStreamAsync(
options.GeneralOptions,
stream,
(s, ct) => this.Decode(options, s, ct),
cancellationToken);
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image;
}
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.

2
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -20,7 +20,7 @@ public sealed class TgaDecoder : ImageDecoder
public static TgaDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -647,7 +647,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadFileHeader(stream);
return new ImageInfo(

2
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -20,7 +20,7 @@ public class TiffDecoder : ImageDecoder
public static TiffDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -213,7 +213,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.inputStream = stream;
DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);

2
src/ImageSharp/Formats/Webp/WebpDecoder.cs

@ -20,7 +20,7 @@ public sealed class WebpDecoder : ImageDecoder
public static WebpDecoder Instance { get; } = new();
/// <inheritdoc/>
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(stream, nameof(stream));

2
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -145,7 +145,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
/// <inheritdoc />
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;

2
src/ImageSharp/IImageInfo.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;

12
src/ImageSharp/Image.Decode.cs

@ -179,12 +179,12 @@ public abstract partial class Image
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentify(DecoderOptions options, Stream stream)
private static (ImageInfo ImageInfo, IImageFormat Format) InternalIdentify(DecoderOptions options, Stream stream)
{
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format);
IImageInfo info = decoder?.Identify(options, stream);
ImageInfo info = decoder?.Identify(options, stream);
return (info, format);
}
@ -195,9 +195,9 @@ public abstract partial class Image
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> InternalIdentifyAsync(
private static async Task<(ImageInfo ImageInfo, IImageFormat Format)> InternalIdentifyAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken)
@ -209,7 +209,7 @@ public abstract partial class Image
return (null, null);
}
IImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
ImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (info, format);
}
}

12
src/ImageSharp/Image.FromBytes.cs

@ -60,9 +60,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(ReadOnlySpan<byte> data) => Identify(data, out IImageFormat _);
public static ImageInfo Identify(ReadOnlySpan<byte> data) => Identify(data, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -72,9 +72,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(ReadOnlySpan<byte> data, out IImageFormat format)
public static ImageInfo Identify(ReadOnlySpan<byte> data, out IImageFormat format)
=> Identify(DecoderOptions.Default, data, out format);
/// <summary>
@ -87,9 +87,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> data, out IImageFormat format)
public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> data, out IImageFormat format)
{
fixed (byte* ptr = data)
{

30
src/ImageSharp/Image.FromFile.cs

@ -42,9 +42,9 @@ public abstract partial class Image
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath)
public static ImageInfo Identify(string filePath)
=> Identify(filePath, out IImageFormat _);
/// <summary>
@ -53,9 +53,9 @@ public abstract partial class Image
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath, out IImageFormat format)
public static ImageInfo Identify(string filePath, out IImageFormat format)
=> Identify(DecoderOptions.Default, filePath, out format);
/// <summary>
@ -66,9 +66,9 @@ public abstract partial class Image
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format)
public static ImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format)
{
Guard.NotNull(options, nameof(options));
using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
@ -83,9 +83,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default)
public static Task<ImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default)
=> IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary>
@ -97,14 +97,14 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(
public static async Task<ImageInfo> IdentifyAsync(
DecoderOptions options,
string filePath,
CancellationToken cancellationToken = default)
{
(IImageInfo ImageInfo, IImageFormat Format) res =
(ImageInfo ImageInfo, IImageFormat Format) res =
await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false);
return res.ImageInfo;
}
@ -117,9 +117,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
string filePath,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken);
@ -133,9 +133,9 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
public static async Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
DecoderOptions options,
string filePath,
CancellationToken cancellationToken = default)

32
src/ImageSharp/Image.FromStream.cs

@ -78,9 +78,9 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Stream stream)
public static ImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <summary>
@ -95,7 +95,7 @@ public abstract partial class Image
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static Task<IImageInfo> IdentifyAsync(Stream stream, CancellationToken cancellationToken = default)
public static Task<ImageInfo> IdentifyAsync(Stream stream, CancellationToken cancellationToken = default)
=> IdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
@ -107,9 +107,9 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Stream stream, out IImageFormat format)
public static ImageInfo Identify(Stream stream, out IImageFormat format)
=> Identify(DecoderOptions.Default, stream, out format);
/// <summary>
@ -122,9 +122,9 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(DecoderOptions options, Stream stream)
public static ImageInfo Identify(DecoderOptions options, Stream stream)
=> Identify(options, stream, out _);
/// <summary>
@ -141,12 +141,12 @@ public abstract partial class Image
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static async Task<IImageInfo> IdentifyAsync(
public static async Task<ImageInfo> IdentifyAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
(ImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
return res.ImageInfo;
}
@ -161,11 +161,11 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found.
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
public static ImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
{
(IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
(ImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
format = data.Format;
return data.ImageInfo;
@ -182,9 +182,9 @@ public abstract partial class Image
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Stream stream,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken);
@ -201,9 +201,9 @@ public abstract partial class Image
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)

4
src/ImageSharp/ImageInfo.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp;
/// <summary>
/// Contains information about the image including dimensions, pixel type information and additional metadata
/// </summary>
internal sealed class ImageInfo : IImageInfo
public sealed class ImageInfo : IImageInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageInfo"/> class.

6
src/ImageSharp/ImageInfoExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp;
@ -13,12 +13,12 @@ public static class ImageInfoExtensions
/// </summary>
/// <param name="info">The image info</param>
/// <returns>The <see cref="Size"/></returns>
public static Size Size(this IImageInfo info) => new Size(info.Width, info.Height);
public static Size Size(this IImageInfo info) => new(info.Width, info.Height);
/// <summary>
/// Gets the bounds of the image.
/// </summary>
/// <param name="info">The image info</param>
/// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle Bounds(this IImageInfo info) => new Rectangle(0, 0, info.Width, info.Height);
public static Rectangle Bounds(this IImageInfo info) => new(0, 0, info.Width, info.Height);
}

20
src/ImageSharp/Metadata/ImageMetadata.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -69,6 +68,10 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
this.XmpProfile = other.XmpProfile?.DeepClone();
// NOTE: This clone is actually shallow but we share the same format
// instances for all images in the configuration.
this.DecodedImageFormat = other.DecodedImageFormat;
}
/// <summary>
@ -137,22 +140,27 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
/// <summary>
/// Gets or sets the Exif profile.
/// </summary>
public ExifProfile ExifProfile { get; set; }
public ExifProfile? ExifProfile { get; set; }
/// <summary>
/// Gets or sets the XMP profile.
/// </summary>
public XmpProfile XmpProfile { get; set; }
public XmpProfile? XmpProfile { get; set; }
/// <summary>
/// Gets or sets the ICC profile.
/// </summary>
public IccProfile IccProfile { get; set; }
public IccProfile? IccProfile { get; set; }
/// <summary>
/// Gets or sets the IPTC profile.
/// </summary>
public IptcProfile IptcProfile { get; set; }
public IptcProfile? IptcProfile { get; set; }
/// <summary>
/// Gets the original format, if any, the image was decode from.
/// </summary>
public IImageFormat? DecodedImageFormat { get; internal set; }
/// <summary>
/// Gets the metadata value associated with the specified key.
@ -165,7 +173,7 @@ public sealed class ImageMetadata : IDeepCloneable<ImageMetadata>
public TFormatMetadata GetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> key)
where TFormatMetadata : class, IDeepCloneable
{
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta))
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
{
return (TFormatMetadata)meta;
}

2
src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs

@ -43,7 +43,7 @@ internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel>
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> destination)
{
ExifProfile profile = destination.Metadata.ExifProfile;
ExifProfile? profile = destination.Metadata.ExifProfile;
if (profile is null)
{
return;

2
src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs

@ -19,7 +19,7 @@ internal static class TransformProcessorHelpers
public static void UpdateDimensionalMetadata<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
ExifProfile profile = image.Metadata.ExifProfile;
ExifProfile? profile = image.Metadata.ExifProfile;
if (profile is null)
{
return;

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -22,7 +22,7 @@ public class IdentifyJpeg
public void ReadImages() => this.jpegBytes ??= File.ReadAllBytes(this.TestImageFullPath);
[Benchmark]
public IImageInfo Identify()
public ImageInfo Identify()
{
using MemoryStream memoryStream = new(this.jpegBytes);
return JpegDecoder.Instance.Identify(DecoderOptions.Default, memoryStream);

4
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -475,7 +475,7 @@ public class BmpDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
}
@ -493,7 +493,7 @@ public class BmpDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);

2
tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs

@ -38,7 +38,7 @@ public class BmpMetadataTests
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata();
Assert.NotNull(bitmapMetadata);

4
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -257,7 +257,7 @@ public class GeneralFormatTests
image.Save(memoryStream, format);
memoryStream.Position = 0;
IImageInfo imageInfo = Image.Identify(memoryStream);
ImageInfo imageInfo = Image.Identify(memoryStream);
Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height);
@ -274,7 +274,7 @@ public class GeneralFormatTests
byte[] invalid = new byte[10];
using MemoryStream memoryStream = new(invalid);
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
ImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
Assert.Null(imageInfo);
Assert.Null(format);

6
tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs

@ -113,7 +113,7 @@ public class GifMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -126,7 +126,7 @@ public class GifMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = await GifDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageInfo image = await GifDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -165,7 +165,7 @@ public class GifMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}

10
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -90,7 +90,7 @@ public partial class JpegDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -103,7 +103,7 @@ public partial class JpegDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = await JpegDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageInfo image = await JpegDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -116,7 +116,7 @@ public partial class JpegDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality);
}
@ -155,7 +155,7 @@ public partial class JpegDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
@ -180,7 +180,7 @@ public partial class JpegDecoderTests
using var stream = new MemoryStream(testFile.Bytes, false);
if (useIdentify)
{
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo);
}
else

6
tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs

@ -35,7 +35,7 @@ public class PbmMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata);
@ -54,7 +54,7 @@ public class PbmMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata);
@ -73,7 +73,7 @@ public class PbmMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata);

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -504,7 +504,7 @@ public partial class PngDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
PngMetadata metadata = imageInfo.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency);
}

8
tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs

@ -219,7 +219,7 @@ public class PngMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo image = PngDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageInfo image = PngDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution);
@ -232,7 +232,7 @@ public class PngMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta);
@ -244,7 +244,7 @@ public class PngMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);
ExifProfile exif = imageInfo.Metadata.ExifProfile;
@ -281,7 +281,7 @@ public class PngMetadataTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile);

2
tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs

@ -41,7 +41,7 @@ public class TgaFileHeaderTests
{
using var stream = new MemoryStream(data);
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
TgaMetadata tgaData = info.Metadata.GetTgaMetadata();
Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel);
Assert.Equal(width, info.Width);

4
tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs

@ -63,7 +63,7 @@ public class BigTiffDecoderTests : TiffDecoderBaseTester
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedWidth, info.Width);
@ -87,7 +87,7 @@ public class BigTiffDecoderTests : TiffDecoderBaseTester
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.NotNull(info.Metadata);
Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder);

4
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -33,7 +33,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedWidth, info.Width);
@ -53,7 +53,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester
TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.NotNull(info.Metadata);
Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder);

4
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -80,7 +80,7 @@ public class TiffMetadataTests
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata();
@ -96,7 +96,7 @@ public class TiffMetadataTests
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata();

2
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -41,7 +41,7 @@ public class WebpDecoderTests
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
IImageInfo imageInfo = Image.Identify(stream);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height);

48
tests/ImageSharp.Tests/Image/ImageTests.Identify.cs

@ -21,7 +21,7 @@ public partial class ImageTests
private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
private IImageInfo LocalImageInfo => this.localImageInfoMock.Object;
private ImageInfo LocalImageInfo => this.localImageInfoMock.Object;
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
@ -38,7 +38,7 @@ public partial class ImageTests
[Fact]
public void FromBytes_GlobalConfiguration()
{
IImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type);
ImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type);
Assert.Equal(ExpectedImageSize, info.Size());
Assert.Equal(ExpectedGlobalFormat, type);
@ -49,7 +49,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type);
ImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -58,7 +58,7 @@ public partial class ImageTests
[Fact]
public void FromFileSystemPath_GlobalConfiguration()
{
IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type);
ImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type);
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
@ -69,7 +69,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type);
ImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -79,7 +79,7 @@ public partial class ImageTests
public void FromStream_GlobalConfiguration()
{
using var stream = new MemoryStream(ActualImageBytes);
IImageInfo info = Image.Identify(stream, out IImageFormat type);
ImageInfo info = Image.Identify(stream, out IImageFormat type);
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
@ -89,7 +89,7 @@ public partial class ImageTests
public void FromStream_GlobalConfiguration_NoFormat()
{
using var stream = new MemoryStream(ActualImageBytes);
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.NotNull(info);
}
@ -100,7 +100,7 @@ public partial class ImageTests
using var stream = new MemoryStream(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type);
ImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type);
Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type);
@ -112,7 +112,7 @@ public partial class ImageTests
using var stream = new MemoryStream(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream);
IImageInfo info = Image.Identify(nonSeekableStream);
ImageInfo info = Image.Identify(nonSeekableStream);
Assert.NotNull(info);
}
@ -122,7 +122,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
ImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
@ -133,7 +133,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.DataStream);
ImageInfo info = Image.Identify(options, this.DataStream);
Assert.Equal(this.LocalImageInfo, info);
}
@ -143,7 +143,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = new() };
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
ImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
Assert.Null(info);
Assert.Null(type);
@ -168,7 +168,7 @@ public partial class ImageTests
0x00, 0x00, 0x00, 0x00
}));
using Stream stream = zipFile.Entries[0].Open();
IImageInfo info = Image.Identify(stream);
ImageInfo info = Image.Identify(stream);
Assert.Null(info);
}
@ -177,7 +177,7 @@ public partial class ImageTests
{
using var stream = new MemoryStream(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info);
}
@ -187,7 +187,7 @@ public partial class ImageTests
{
using var stream = new MemoryStream(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
@ -200,7 +200,7 @@ public partial class ImageTests
using var nonSeekableStream = new NonSeekableStream(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info);
}
@ -212,7 +212,7 @@ public partial class ImageTests
using var nonSeekableStream = new NonSeekableStream(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
@ -237,7 +237,7 @@ public partial class ImageTests
0x00, 0x00, 0x00, 0x00
}));
using Stream stream = zipFile.Entries[0].Open();
IImageInfo info = await Image.IdentifyAsync(stream);
ImageInfo info = await Image.IdentifyAsync(stream);
Assert.Null(info);
}
@ -246,7 +246,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info);
}
@ -255,7 +255,7 @@ public partial class ImageTests
{
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
(IImageInfo ImageInfo, IImageFormat Format) info =
(ImageInfo ImageInfo, IImageFormat Format) info =
await Image.IdentifyWithFormatAsync(options, this.MockFilePath);
Assert.NotNull(info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format);
@ -264,7 +264,7 @@ public partial class ImageTests
[Fact]
public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration()
{
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath);
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format);
@ -273,7 +273,7 @@ public partial class ImageTests
[Fact]
public async Task FromPathAsync_GlobalConfiguration()
{
IImageInfo info = await Image.IdentifyAsync(ActualImagePath);
ImageInfo info = await Image.IdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, info.Size());
}
@ -284,7 +284,7 @@ public partial class ImageTests
DecoderOptions options = new() { Configuration = this.LocalConfiguration };
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format)
(ImageInfo ImageInfo, IImageFormat Format)
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo);
@ -297,7 +297,7 @@ public partial class ImageTests
DecoderOptions options = new() { Configuration = new() };
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format)
(ImageInfo ImageInfo, IImageFormat Format)
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Null(info.ImageInfo);

4
tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs

@ -25,7 +25,7 @@ public partial class ImageTests
protected Mock<IImageFormat> localImageFormatMock;
protected Mock<IImageInfo> localImageInfoMock;
protected Mock<ImageInfo> localImageInfoMock;
protected readonly string MockFilePath = Guid.NewGuid().ToString();
@ -56,7 +56,7 @@ public partial class ImageTests
this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1);
this.localStreamReturnImageAgnostic = new Image<Bgra4444>(1, 1);
this.localImageInfoMock = new Mock<IImageInfo>();
this.localImageInfoMock = new Mock<ImageInfo>();
this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<IImageDecoder>();

10
tests/ImageSharp.Tests/TestFormat.cs

@ -199,8 +199,12 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat
public bool IsSupportedFileFormat(Span<byte> header) => this.testFormat.IsSupportedFileFormat(header);
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Image<TestPixelForAgnosticDecode> image =
this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
}
protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)
=> new() { GeneralOptions = options };
@ -208,7 +212,7 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat
protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Configuration configuration = options.GeneralOptions.Configuration;
var ms = new MemoryStream();
using MemoryStream ms = new();
stream.CopyTo(ms, configuration.StreamProcessingBufferSize);
byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray();
this.testFormat.DecodeCalls.Add(new DecodeOperation

7
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -72,8 +72,11 @@ public class MagickReferenceDecoder : ImageDecoder
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(options, stream, cancellationToken);
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Image<Rgba32> image = this.Decode<Rgba32>(options, stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
}
private static void FromRgba32Bytes<TPixel>(Configuration configuration, Span<byte> rgbaBytes, IMemoryGroup<TPixel> destinationGroup)
where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel<TPixel>

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

@ -13,7 +13,7 @@ public class SystemDrawingReferenceDecoder : ImageDecoder
{
public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder();
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
using SDBitmap sourceBitmap = new(stream);
PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));

14
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -363,8 +363,11 @@ public class TestImageProviderTests
}
}
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
}
protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
@ -403,8 +406,11 @@ public class TestImageProviderTests
}
}
protected override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
=> this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
}
protected override Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)
{

Loading…
Cancel
Save