Browse Source

Refactor Identify and DetectFormat and fix tests

pull/2317/head
James Jackson-South 4 years ago
parent
commit
0aa12a4602
  1. 24
      src/ImageSharp/Common/Attempt{T}.cs
  2. 18
      src/ImageSharp/Formats/ImageDecoder.cs
  3. 79
      src/ImageSharp/Image.Decode.cs
  4. 49
      src/ImageSharp/Image.FromBytes.cs
  5. 134
      src/ImageSharp/Image.FromFile.cs
  6. 210
      src/ImageSharp/Image.FromStream.cs
  7. 16
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  8. 33
      tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
  9. 12
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  10. 18
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  11. 23
      tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
  12. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  13. 46
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  14. 6
      tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
  15. 50
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs
  16. 40
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  17. 28
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  18. 14
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  19. 2
      tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
  20. 55
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  21. 152
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  22. 14
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  23. 3
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  24. 8
      tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs

24
src/ImageSharp/Common/Attempt{T}.cs

@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp;
/// <summary>
/// A wrapper for nullable values that correctly handles the return type based on the result.
/// </summary>
/// <typeparam name="T">The type of nullable value.</typeparam>
public readonly struct Attempt<T>
{
/// <summary>
/// Gets a value indicating whether the attempted return was successful.
/// </summary>
[MemberNotNullWhen(returnValue: true, member: nameof(Value))]
public bool Success => this.Value is not null;
/// <summary>
/// Gets the value when the attempted return is successful; otherwise, the default value for the type.
/// </summary>
public T? Value { get; init; }
}

18
src/ImageSharp/Formats/ImageDecoder.cs

@ -84,12 +84,18 @@ public abstract class ImageDecoder : IImageDecoder
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<ImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) public async Task<ImageInfo> IdentifyAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default)
=> WithSeekableMemoryStreamAsync( {
options, ImageInfo info = await WithSeekableMemoryStreamAsync(
stream, options,
(s, ct) => this.Identify(options, s, ct), stream,
cancellationToken); (s, ct) => this.Identify(options, s, ct),
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, info);
return info;
}
/// <summary> /// <summary>
/// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type. /// Decodes the image from the specified stream to an <see cref="Image{TPixel}" /> of a specific pixel type.

79
src/ImageSharp/Image.Decode.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -45,7 +44,7 @@ public abstract partial class Image
/// <param name="configuration">The general configuration.</param> /// <param name="configuration">The general configuration.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
private static IImageFormat InternalDetectFormat(Configuration configuration, Stream stream) private static IImageFormat? InternalDetectFormat(Configuration configuration, Stream stream)
{ {
// We take a minimum of the stream length vs the max header size and always check below // We take a minimum of the stream length vs the max header size and always check below
// to ensure that only formats that headers fit within the given buffer length are tested. // to ensure that only formats that headers fit within the given buffer length are tested.
@ -77,15 +76,12 @@ public abstract partial class Image
// Does the given stream contain enough data to fit in the header for the format // Does the given stream contain enough data to fit in the header for the format
// and does that data match the format specification? // and does that data match the format specification?
// Individual formats should still check since they are public. // Individual formats should still check since they are public.
IImageFormat format = null; IImageFormat? format = null;
foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors)
{ {
if (formatDetector.HeaderSize <= headerSize) if (formatDetector.HeaderSize <= headerSize && formatDetector.TryDetectFormat(headersBuffer, out IImageFormat? attemptFormat))
{ {
if (formatDetector.TryDetectFormat(headersBuffer, out IImageFormat attemptFormat)) format = attemptFormat;
{
format = attemptFormat;
}
} }
} }
@ -97,13 +93,11 @@ public abstract partial class Image
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The IImageFormat.</param> /// <returns>The <see cref="IImageDecoder"/> or <see langword="null"/>.</returns>
/// <returns>The image format or null if none found.</returns> private static IImageDecoder? DiscoverDecoder(DecoderOptions options, Stream stream)
private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format)
{ {
format = InternalDetectFormat(options.Configuration, stream); IImageFormat? format = InternalDetectFormat(options.Configuration, stream);
return format is not null
return format != null
? options.Configuration.ImageFormatsManager.FindDecoder(format) ? options.Configuration.ImageFormatsManager.FindDecoder(format)
: null; : null;
} }
@ -117,60 +111,56 @@ public abstract partial class Image
/// <returns> /// <returns>
/// A new <see cref="Image{TPixel}"/>. /// A new <see cref="Image{TPixel}"/>.
/// </returns> /// </returns>
private static (Image<TPixel> Image, IImageFormat Format) Decode<TPixel>(DecoderOptions options, Stream stream) private static Image<TPixel>? Decode<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return null;
} }
Image<TPixel> img = decoder.Decode<TPixel>(options, stream); return decoder.Decode<TPixel>(options, stream);
return (img, format);
} }
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>( private static async Task<Image<TPixel>?> DecodeAsync<TPixel>(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken) CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return null;
} }
Image<TPixel> img = await decoder.DecodeAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false); return await decoder.DecodeAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
return (img, format);
} }
private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream) private static Image? Decode(DecoderOptions options, Stream stream)
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return null;
} }
Image img = decoder.Decode(options, stream); return decoder.Decode(options, stream);
return (img, format);
} }
private static async Task<(Image Image, IImageFormat Format)> DecodeAsync( private static async Task<Image?> DecodeAsync(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return null;
} }
Image img = await decoder.DecodeAsync(options, stream, cancellationToken).ConfigureAwait(false); return await decoder.DecodeAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (img, format);
} }
/// <summary> /// <summary>
@ -181,11 +171,15 @@ public abstract partial class Image
/// <returns> /// <returns>
/// The <see cref="ImageInfo"/> 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> /// </returns>
private static (ImageInfo ImageInfo, IImageFormat Format) InternalIdentify(DecoderOptions options, Stream stream) private static ImageInfo? InternalIdentify(DecoderOptions options, Stream stream)
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
ImageInfo info = decoder?.Identify(options, stream); if (decoder is null)
return (info, format); {
return null;
}
return decoder.Identify(options, stream);
} }
/// <summary> /// <summary>
@ -197,19 +191,18 @@ public abstract partial class Image
/// <returns> /// <returns>
/// The <see cref="ImageInfo"/> 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> /// </returns>
private static async Task<(ImageInfo ImageInfo, IImageFormat Format)> InternalIdentifyAsync( private static async Task<ImageInfo?> InternalIdentifyAsync(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder? decoder = DiscoverDecoder(options, stream);
if (decoder is null) if (decoder is null)
{ {
return (null, null); return null;
} }
ImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false); return await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (info, format);
} }
} }

49
src/ImageSharp/Image.FromBytes.cs

@ -55,46 +55,41 @@ public abstract partial class Image
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// A return value indicates whether the operation succeeded.
/// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
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.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="info">
/// When this method returns, contains the raw image information;
/// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception> /// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> public static bool TryIdentify(ReadOnlySpan<byte> data, [NotNullWhen(true)] out ImageInfo? info)
/// The <see cref="ImageInfo"/> or null if suitable info detector not found. => TryIdentify(DecoderOptions.Default, data, out info);
/// </returns>
public static ImageInfo Identify(ReadOnlySpan<byte> data, out IImageFormat format)
=> Identify(DecoderOptions.Default, data, out format);
/// <summary> /// <summary>
/// Reads the raw image information from the specified span of bytes without fully decoding it. /// Reads the raw image information from the specified span of bytes without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing encoded image data to read the header from.</param> /// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="info">
/// When this method returns, contains the raw image information;
/// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception> /// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> public static unsafe bool TryIdentify(DecoderOptions options, ReadOnlySpan<byte> data, [NotNullWhen(true)] out ImageInfo? info)
/// The <see cref="ImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> data, out IImageFormat format)
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, data.Length);
return Identify(options, stream, out format); return TryIdentify(options, stream, out info);
} }
} }
@ -141,7 +136,7 @@ public abstract partial class Image
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, data.Length);
return Load<TPixel>(options, stream); return Load<TPixel>(options, stream);
} }
} }
@ -166,7 +161,7 @@ public abstract partial class Image
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, data.Length);
return Load<TPixel>(options, stream, out format); return Load<TPixel>(options, stream, out format);
} }
} }
@ -222,7 +217,7 @@ public abstract partial class Image
{ {
fixed (byte* ptr = data) fixed (byte* ptr = data)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, data.Length);
return Load(options, stream, out format); return Load(options, stream, out format);
} }
} }

134
src/ImageSharp/Image.FromFile.cs

@ -13,22 +13,32 @@ namespace SixLabors.ImageSharp;
public abstract partial class Image public abstract partial class Image
{ {
/// <summary> /// <summary>
/// By reading the header on the provided file this calculates the images mime type. /// Detects the encoded image format type from the specified file.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The mime type or null if none found.</param> /// <param name="format">
/// <returns>returns true when format was detected otherwise false.</returns> /// When this method returns, contains the format that matches the given file;
/// otherwise, the default value for the type of the <paramref name="format"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if a match is found; otherwise, <see langword="false"/></returns>
public static bool TryDetectFormat(string filePath, [NotNullWhen(true)] out IImageFormat? format) public static bool TryDetectFormat(string filePath, [NotNullWhen(true)] out IImageFormat? format)
=> TryDetectFormat(DecoderOptions.Default, filePath, out format); => TryDetectFormat(DecoderOptions.Default, filePath, out format);
/// <summary> /// <summary>
/// By reading the header on the provided file this calculates the images mime type. /// Detects the encoded image format type from the specified file.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The mime type or null if none found.</param> /// <param name="format">
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// When this method returns, contains the format that matches the given file;
/// <returns>returns true when format was detected otherwise false.</returns> /// otherwise, the default value for the type of the <paramref name="format"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if a match is found; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The options are null.</exception>
public static bool TryDetectFormat(DecoderOptions options, string filePath, [NotNullWhen(true)] out IImageFormat? format) public static bool TryDetectFormat(DecoderOptions options, string filePath, [NotNullWhen(true)] out IImageFormat? format)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -38,112 +48,106 @@ public abstract partial class Image
} }
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Detects the encoded image format type from the specified file.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// The <see cref="ImageInfo"/> or null if suitable info detector not found. /// <returns>A <see cref="Task{Attempt}"/> representing the asynchronous operation.</returns>
/// </returns> public static Task<Attempt<IImageFormat>> TryDetectFormatAsync(
public static ImageInfo Identify(string filePath) string filePath,
=> Identify(filePath, out IImageFormat _); CancellationToken cancellationToken = default)
=> TryDetectFormatAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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="ImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static ImageInfo Identify(string filePath, out IImageFormat format)
=> Identify(DecoderOptions.Default, filePath, out format);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Detects the encoded image format type from the specified file.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <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> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns> /// <returns>A <see cref="Task{Attempt}"/> representing the asynchronous operation.</returns>
/// The <see cref="ImageInfo"/> or null if suitable info detector is not found. public static async Task<Attempt<IImageFormat>> TryDetectFormatAsync(
/// </returns> DecoderOptions options,
public static ImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format) string filePath,
CancellationToken cancellationToken = default)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
return Identify(options, file, out format); using Stream stream = options.Configuration.FileSystem.OpenRead(filePath);
return await TryDetectFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified file path without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="info">
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// When this method returns, contains the raw image information;
/// <returns> /// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// This parameter is passed uninitialized.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found. /// </param>
/// </returns> /// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
public static Task<ImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default) public static bool TryIdentify(string filePath, [NotNullWhen(true)] out ImageInfo? info)
=> IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken); => TryIdentify(DecoderOptions.Default, filePath, out info);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified file path without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="info">
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// When this method returns, contains the raw image information;
/// <returns> /// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// This parameter is passed uninitialized.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found. /// </param>
/// </returns> /// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
public static async Task<ImageInfo> IdentifyAsync( /// <exception cref="ArgumentNullException">The options are null.</exception>
DecoderOptions options, public static bool TryIdentify(DecoderOptions options, string filePath, [NotNullWhen(true)] out ImageInfo? info)
string filePath,
CancellationToken cancellationToken = default)
{ {
(ImageInfo ImageInfo, IImageFormat Format) res = Guard.NotNull(options, nameof(options));
await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false);
return res.ImageInfo; using Stream stream = options.Configuration.FileSystem.OpenRead(filePath);
return TryIdentify(options, stream, out info);
} }
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{Attempt}"/> representing the asynchronous operation.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static Task<Attempt<ImageInfo>> TryIdentifyAsync(
string filePath, string filePath,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken); => TryIdentifyAsync(DecoderOptions.Default, filePath, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{Attempt}"/> representing the asynchronous operation.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static async Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static async Task<Attempt<ImageInfo>> TryIdentifyAsync(
DecoderOptions options, DecoderOptions options,
string filePath, string filePath,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
using Stream stream = options.Configuration.FileSystem.OpenRead(filePath); using Stream stream = options.Configuration.FileSystem.OpenRead(filePath);
return await IdentifyWithFormatAsync(options, stream, cancellationToken) return await TryIdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
} }
/// <summary> /// <summary>

210
src/ImageSharp/Image.FromStream.cs

@ -16,45 +16,59 @@ namespace SixLabors.ImageSharp;
public abstract partial class Image public abstract partial class Image
{ {
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// Detects the encoded image format type from the specified stream.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type or null if none found.</param> /// <param name="format">
/// When this method returns, contains the format that matches the given stream;
/// otherwise, the default value for the type of the <paramref name="format"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if a match is found; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>returns true when format was detected otherwise false.</returns>
public static bool TryDetectFormat(Stream stream, [NotNullWhen(true)] out IImageFormat? format) public static bool TryDetectFormat(Stream stream, [NotNullWhen(true)] out IImageFormat? format)
=> TryDetectFormat(DecoderOptions.Default, stream, out format); => TryDetectFormat(DecoderOptions.Default, stream, out format);
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// Detects the encoded image format type from the specified stream.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type or null if none found.</param> /// <param name="format">
/// When this method returns, contains the format that matches the given stream;
/// otherwise, the default value for the type of the <paramref name="format"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if a match is found; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>returns true when format was detected otherwise false.</returns>
public static bool TryDetectFormat(DecoderOptions options, Stream stream, [NotNullWhen(true)] out IImageFormat? format) public static bool TryDetectFormat(DecoderOptions options, Stream stream, [NotNullWhen(true)] out IImageFormat? format)
{ {
format = WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); format = WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s));
return format != null; return format is not null;
} }
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// Detects the encoded image format type from the specified stream.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation or null if none is found.</returns> /// <returns>A <see cref="Task{Attempt}"/> representing the asynchronous operation.</returns>
public static Task<IImageFormat> DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) public static Task<Attempt<IImageFormat>> TryDetectFormatAsync(
=> DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); Stream stream,
CancellationToken cancellationToken = default)
=> TryDetectFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// Detects the encoded image format type from the specified stream.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
@ -62,117 +76,63 @@ public abstract partial class Image
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Attempt}"/> representing the asynchronous operation.</returns>
public static Task<IImageFormat> DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) public static async Task<Attempt<IImageFormat>> TryDetectFormatAsync(
=> WithSeekableStreamAsync( DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{
IImageFormat? format = await WithSeekableStreamAsync(
options, options,
stream, stream,
(s, _) => Task.FromResult(InternalDetectFormat(options.Configuration, s)), (s, _) => Task.FromResult(InternalDetectFormat(options.Configuration, s)),
cancellationToken); cancellationToken).ConfigureAwait(false);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static ImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <summary> return new() { Value = format };
/// Reads the raw image information from the specified stream without fully decoding it. }
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static Task<ImageInfo> IdentifyAsync(Stream stream, CancellationToken cancellationToken = default)
=> IdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="info">
/// When this method returns, contains the raw image information;
/// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> public static bool TryIdentify(Stream stream, [NotNullWhen(true)] out ImageInfo? info)
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found. => TryIdentify(DecoderOptions.Default, stream, out info);
/// </returns>
public static ImageInfo Identify(Stream stream, out IImageFormat format)
=> Identify(DecoderOptions.Default, stream, out format);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="info">
/// When this method returns, contains the raw image information;
/// otherwise, the default value for the type of the <paramref name="info"/> parameter.
/// This parameter is passed uninitialized.
/// </param>
/// <returns><see langword="true"/> if the information can be read; otherwise, <see langword="false"/></returns>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> public static bool TryIdentify(DecoderOptions options, Stream stream, [NotNullWhen(true)] out ImageInfo? info)
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static ImageInfo Identify(DecoderOptions options, Stream stream)
=> Identify(options, stream, out _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if
/// a suitable detector is not found.
/// </returns>
public static async Task<ImageInfo> IdentifyAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{ {
(ImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); info = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
return res.ImageInfo; return info is not null;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>
/// The <see cref="ImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static ImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
{
(ImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
format = data.Format;
return data.ImageInfo;
} }
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
@ -181,16 +141,16 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{Attempt}"/> representing the asynchronous operation.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static Task<Attempt<ImageInfo>> TryIdentifyAsync(
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); => TryIdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// A return value indicates whether the operation succeeded.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
@ -200,18 +160,21 @@ public abstract partial class Image
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{Attempt}"/> representing the asynchronous operation.
/// <see cref="ImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(ImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( public static async Task<Attempt<ImageInfo>> TryIdentifyAsync(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync( {
ImageInfo? info = await WithSeekableStreamAsync(
options, options,
stream, stream,
(s, ct) => InternalIdentifyAsync(options, s, ct), (s, ct) => InternalIdentifyAsync(options, s, ct),
cancellationToken); cancellationToken).ConfigureAwait(false);
return new() { Value = info };
}
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -387,16 +350,15 @@ public abstract partial class Image
public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream, out IImageFormat format) public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(Image<TPixel> Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode<TPixel>(options, s)); Image<TPixel>? image = WithSeekableStream(options, stream, s => Decode<TPixel>(options, s));
format = data.Format; if (image is null)
if (data.Image is null)
{ {
ThrowNotLoaded(options); ThrowNotLoaded(options);
} }
return data.Image; format = image.Metadata.DecodedImageFormat!;
return image;
} }
/// <summary> /// <summary>
@ -416,16 +378,15 @@ public abstract partial class Image
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
(Image Image, IImageFormat Format) data = Image? image = await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken)
await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken) .ConfigureAwait(false);
.ConfigureAwait(false);
if (data.Image is null) if (image is null)
{ {
ThrowNotLoaded(options); ThrowNotLoaded(options);
} }
return data; return new(image, image.Metadata.DecodedImageFormat!);
} }
/// <summary> /// <summary>
@ -447,16 +408,15 @@ public abstract partial class Image
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
(Image<TPixel> Image, IImageFormat Format) data = Image<TPixel>? image = await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync<TPixel>(options, s, ct), cancellationToken)
await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync<TPixel>(options, s, ct), cancellationToken) .ConfigureAwait(false);
.ConfigureAwait(false);
if (data.Image is null) if (image is null)
{ {
ThrowNotLoaded(options); ThrowNotLoaded(options);
} }
return data; return new(image, image.Metadata.DecodedImageFormat!);
} }
/// <summary> /// <summary>
@ -498,16 +458,14 @@ public abstract partial class Image
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format)
{ {
(Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s)); Image? image = WithSeekableStream(options, stream, s => Decode(options, s));
if (image is null)
format = fmt;
if (img is null)
{ {
ThrowNotLoaded(options); ThrowNotLoaded(options);
} }
return img; format = image.Metadata.DecodedImageFormat!;
return image;
} }
/// <summary> /// <summary>

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

@ -473,9 +473,9 @@ public class BmpDecoderTests
[InlineData(Bit1Pal1, 1)] [InlineData(Bit1Pal1, 1)]
public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel);
} }
@ -491,9 +491,9 @@ public class BmpDecoderTests
[InlineData(RLE8Inverted, 491, 272)] [InlineData(RLE8Inverted, 491, 272)]
public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height); Assert.Equal(expectedHeight, imageInfo.Height);
@ -503,8 +503,8 @@ public class BmpDecoderTests
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> image = BmpDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream); using Image<Rgba32> image = BmpDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);

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

@ -14,8 +14,9 @@ public class BmpMetadataTests
[Fact] [Fact]
public void CloneIsDeep() public void CloneIsDeep()
{ {
var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 }; BmpMetadata meta = new()
var clone = (BmpMetadata)meta.DeepClone(); { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 };
BmpMetadata clone = (BmpMetadata)meta.DeepClone();
clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; clone.BitsPerPixel = BmpBitsPerPixel.Pixel32;
clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2; clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2;
@ -35,15 +36,13 @@ public class BmpMetadataTests
[InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)]
public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using MemoryStream stream = new(testFile.Bytes, false);
{ Image.TryIdentify(stream, out ImageInfo imageInfo);
ImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo); BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata();
BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); Assert.NotNull(bitmapMetadata);
Assert.NotNull(bitmapMetadata); Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType);
Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType);
}
} }
[Theory] [Theory]
@ -51,12 +50,10 @@ public class BmpMetadataTests
public void Decoder_CanReadColorProfile<TPixel>(TestImageProvider<TPixel> provider) public void Decoder_CanReadColorProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(BmpDecoder.Instance)) using Image<TPixel> image = provider.GetImage(BmpDecoder.Instance);
{ ImageSharp.Metadata.ImageMetadata metaData = image.Metadata;
ImageSharp.Metadata.ImageMetadata metaData = image.Metadata; Assert.NotNull(metaData);
Assert.NotNull(metaData); Assert.NotNull(metaData.IccProfile);
Assert.NotNull(metaData.IccProfile); Assert.Equal(16, metaData.IccProfile.Entries.Length);
Assert.Equal(16, metaData.IccProfile.Entries.Length);
}
} }
} }

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

@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Formats; namespace SixLabors.ImageSharp.Tests.Formats;
@ -257,15 +256,11 @@ public class GeneralFormatTests
image.Save(memoryStream, format); image.Save(memoryStream, format);
memoryStream.Position = 0; memoryStream.Position = 0;
ImageInfo imageInfo = Image.Identify(memoryStream); Image.TryIdentify(memoryStream, out ImageInfo imageInfo);
Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Width, width);
Assert.Equal(imageInfo.Height, height); Assert.Equal(imageInfo.Height, height);
memoryStream.Position = 0; Assert.Equal(format, imageInfo.Metadata.DecodedImageFormat);
imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat);
Assert.Equal(format, detectedFormat);
} }
[Fact] [Fact]
@ -274,10 +269,9 @@ public class GeneralFormatTests
byte[] invalid = new byte[10]; byte[] invalid = new byte[10];
using MemoryStream memoryStream = new(invalid); using MemoryStream memoryStream = new(invalid);
ImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); Image.TryIdentify(memoryStream, out ImageInfo imageInfo);
Assert.Null(imageInfo); Assert.Null(imageInfo);
Assert.Null(format);
} }
private static IImageFormat GetFormat(string format) private static IImageFormat GetFormat(string format)

18
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -59,13 +59,13 @@ public class GifDecoderTests
[Fact] [Fact]
public unsafe void Decode_NonTerminatedFinalFrame() public unsafe void Decode_NonTerminatedFinalFrame()
{ {
var testFile = TestFile.Create(TestImages.Gif.Rings); TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
int length = testFile.Bytes.Length - 2; int length = testFile.Bytes.Length - 2;
fixed (byte* data = testFile.Bytes.AsSpan(0, length)) fixed (byte* data = testFile.Bytes.AsSpan(0, length))
{ {
using var stream = new UnmanagedMemoryStream(data, length); using UnmanagedMemoryStream stream = new(data, length);
using Image<Rgba32> image = GifDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream); using Image<Rgba32> image = GifDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream);
Assert.Equal((200, 200), (image.Width, image.Height)); Assert.Equal((200, 200), (image.Width, image.Height));
} }
@ -120,9 +120,13 @@ public class GifDecoderTests
[InlineData(TestImages.Gif.Trans, 8)] [InlineData(TestImages.Gif.Trans, 8)]
public void DetectPixelSize(string imagePath, int expectedPixelSize) public void DetectPixelSize(string imagePath, int expectedPixelSize)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
} }
[Theory] [Theory]
@ -155,9 +159,9 @@ public class GifDecoderTests
[Fact] [Fact]
public void CanDecodeIntermingledImages() public void CanDecodeIntermingledImages()
{ {
using (var kumin1 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes)) using (Image<Rgba32> kumin1 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes))
using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes))
using (var kumin2 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes)) using (Image<Rgba32> kumin2 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes))
{ {
for (int i = 0; i < kumin1.Frames.Count; i++) for (int i = 0; i < kumin1.Frames.Count; i++)
{ {

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

@ -13,8 +13,9 @@ public class PbmMetadataTests
[Fact] [Fact]
public void CloneIsDeep() public void CloneIsDeep()
{ {
var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; PbmMetadata meta = new()
var clone = (PbmMetadata)meta.DeepClone(); { ColorType = PbmColorType.Grayscale };
PbmMetadata clone = (PbmMetadata)meta.DeepClone();
clone.ColorType = PbmColorType.Rgb; clone.ColorType = PbmColorType.Rgb;
clone.ComponentType = PbmComponentType.Short; clone.ComponentType = PbmComponentType.Short;
@ -33,9 +34,9 @@ public class PbmMetadataTests
[InlineData(RgbPlain, PbmEncoding.Plain)] [InlineData(RgbPlain, PbmEncoding.Plain)]
public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata); Assert.NotNull(bitmapMetadata);
@ -52,9 +53,9 @@ public class PbmMetadataTests
[InlineData(RgbPlain, PbmColorType.Rgb)] [InlineData(RgbPlain, PbmColorType.Rgb)]
public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata); Assert.NotNull(bitmapMetadata);
@ -71,9 +72,9 @@ public class PbmMetadataTests
[InlineData(RgbPlain, PbmComponentType.Byte)] [InlineData(RgbPlain, PbmComponentType.Byte)]
public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata); Assert.NotNull(bitmapMetadata);

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

@ -82,7 +82,7 @@ public partial class PngDecoderTests
public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type)
{ {
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path);
using var image = Image.Load(file); using Image image = Image.Load(file);
Assert.IsType(type, image); Assert.IsType(type, image);
} }
@ -304,9 +304,13 @@ public partial class PngDecoderTests
[InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)]
public void Identify(string imagePath, int expectedPixelSize) public void Identify(string imagePath, int expectedPixelSize)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel);
Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
} }
[Theory] [Theory]
@ -502,9 +506,9 @@ public partial class PngDecoderTests
[InlineData(TestImages.Png.Issue2209IndexedWithTransparency)] [InlineData(TestImages.Png.Issue2209IndexedWithTransparency)]
public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath) public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); PngMetadata metadata = imageInfo.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency); Assert.True(metadata.HasTransparency);
} }

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

@ -23,7 +23,7 @@ public class PngMetadataTests
[Fact] [Fact]
public void CloneIsDeep() public void CloneIsDeep()
{ {
var meta = new PngMetadata PngMetadata meta = new()
{ {
BitDepth = PngBitDepth.Bit16, BitDepth = PngBitDepth.Bit16,
ColorType = PngColorType.GrayscaleWithAlpha, ColorType = PngColorType.GrayscaleWithAlpha,
@ -32,7 +32,7 @@ public class PngMetadataTests
TextData = new List<PngTextData> { new PngTextData("name", "value", "foo", "bar") } TextData = new List<PngTextData> { new PngTextData("name", "value", "foo", "bar") }
}; };
var clone = (PngMetadata)meta.DeepClone(); PngMetadata clone = (PngMetadata)meta.DeepClone();
clone.BitDepth = PngBitDepth.Bit2; clone.BitDepth = PngBitDepth.Bit2;
clone.ColorType = PngColorType.Palette; clone.ColorType = PngColorType.Palette;
@ -63,7 +63,7 @@ public class PngMetadataTests
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> input = provider.GetImage(PngDecoder.Instance); using Image<TPixel> input = provider.GetImage(PngDecoder.Instance);
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
input.Save(memoryStream, new PngEncoder()); input.Save(memoryStream, new PngEncoder());
memoryStream.Position = 0; memoryStream.Position = 0;
@ -93,13 +93,13 @@ public class PngMetadataTests
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> input = provider.GetImage(PngDecoder.Instance); using Image<TPixel> input = provider.GetImage(PngDecoder.Instance);
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
// This will be a zTXt chunk. // This will be a zTXt chunk.
var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); PngTextData expectedText = new("large-text", new string('c', 100), string.Empty, string.Empty);
// This will be a iTXt chunk. // This will be a iTXt chunk.
var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); PngTextData expectedTextNoneLatin = new("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword");
PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance);
inputMetadata.TextData.Add(expectedText); inputMetadata.TextData.Add(expectedText);
inputMetadata.TextData.Add(expectedTextNoneLatin); inputMetadata.TextData.Add(expectedTextNoneLatin);
@ -153,7 +153,7 @@ public class PngMetadataTests
SkipMetadata = false SkipMetadata = false
}; };
var testFile = TestFile.Create(TestImages.Png.Blur); TestFile testFile = TestFile.Create(TestImages.Png.Blur);
using Image<Rgba32> image = testFile.CreateRgba32Image(PngDecoder.Instance, options); using Image<Rgba32> image = testFile.CreateRgba32Image(PngDecoder.Instance, options);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
@ -172,7 +172,7 @@ public class PngMetadataTests
SkipMetadata = true SkipMetadata = true
}; };
var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); TestFile testFile = TestFile.Create(TestImages.Png.PngWithMetadata);
using Image<Rgba32> image = testFile.CreateRgba32Image(PngDecoder.Instance, options); using Image<Rgba32> image = testFile.CreateRgba32Image(PngDecoder.Instance, options);
PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance);
@ -183,8 +183,8 @@ public class PngMetadataTests
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> image = PngDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream); using Image<Rgba32> image = PngDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
@ -201,11 +201,11 @@ public class PngMetadataTests
ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
byte[] expectedProfileBytes = expectedProfile.ToByteArray(); byte[] expectedProfileBytes = expectedProfile.ToByteArray();
using var memStream = new MemoryStream(); using MemoryStream memStream = new();
input.Save(memStream, new PngEncoder()); input.Save(memStream, new PngEncoder());
memStream.Position = 0; memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream); using Image<Rgba32> output = Image.Load<Rgba32>(memStream);
ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
byte[] actualProfileBytes = actualProfile.ToByteArray(); byte[] actualProfileBytes = actualProfile.ToByteArray();
@ -217,8 +217,8 @@ public class PngMetadataTests
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo image = PngDecoder.Instance.Identify(DecoderOptions.Default, stream); ImageInfo image = PngDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
@ -230,9 +230,9 @@ public class PngMetadataTests
[InlineData(TestImages.Png.PngWithMetadata)] [InlineData(TestImages.Png.PngWithMetadata)]
public void Identify_ReadsTextData(string imagePath) public void Identify_ReadsTextData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance);
VerifyTextDataIsPresent(meta); VerifyTextDataIsPresent(meta);
@ -242,9 +242,9 @@ public class PngMetadataTests
[InlineData(TestImages.Png.PngWithMetadata)] [InlineData(TestImages.Png.PngWithMetadata)]
public void Identify_ReadsExifData(string imagePath) public void Identify_ReadsExifData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile); Assert.NotNull(imageInfo.Metadata.ExifProfile);
ExifProfile exif = imageInfo.Metadata.ExifProfile; ExifProfile exif = imageInfo.Metadata.ExifProfile;
@ -279,9 +279,9 @@ public class PngMetadataTests
[InlineData(TestImages.Png.Issue1875)] [InlineData(TestImages.Png.Issue1875)]
public void Identify_ReadsLegacyExifData(string imagePath) public void Identify_ReadsLegacyExifData(string imagePath)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile); Assert.NotNull(imageInfo.Metadata.ExifProfile);

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

@ -22,7 +22,7 @@ public class TgaFileHeaderTests
[InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 32, 8 })] // invalid height [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 32, 8 })] // invalid height
public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatException(byte[] data) public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatException(byte[] data)
{ {
using var stream = new MemoryStream(data); using MemoryStream stream = new(data);
Assert.Throws<UnknownImageFormatException>(() => Assert.Throws<UnknownImageFormatException>(() =>
{ {
@ -39,9 +39,9 @@ public class TgaFileHeaderTests
[InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)] [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)]
public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel) public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel)
{ {
using var stream = new MemoryStream(data); using MemoryStream stream = new(data);
ImageInfo info = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo info);
TgaMetadata tgaData = info.Metadata.GetTgaMetadata(); TgaMetadata tgaData = info.Metadata.GetTgaMetadata();
Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel); Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel);
Assert.Equal(width, info.Width); Assert.Equal(width, info.Width);

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

@ -60,23 +60,21 @@ public class BigTiffDecoderTests : TiffDecoderBaseTester
[InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using MemoryStream stream = new(testFile.Bytes, false);
{ Image.TryIdentify(stream, out ImageInfo info);
ImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); Assert.Equal(expectedWidth, info.Width);
Assert.Equal(expectedWidth, info.Width); Assert.Equal(expectedHeight, info.Height);
Assert.Equal(expectedHeight, info.Height); Assert.NotNull(info.Metadata);
Assert.NotNull(info.Metadata); Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution);
Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution);
Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata();
TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); Assert.NotNull(tiffmeta);
Assert.NotNull(tiffmeta); Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType);
Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType);
}
} }
[Theory] [Theory]
@ -84,19 +82,17 @@ public class BigTiffDecoderTests : TiffDecoderBaseTester
[InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)] [InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)]
public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) public void ByteOrder(string imagePath, ByteOrder expectedByteOrder)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using MemoryStream stream = new(testFile.Bytes, false);
{ Image.TryIdentify(stream, out ImageInfo info);
ImageInfo info = Image.Identify(stream);
Assert.NotNull(info.Metadata); Assert.NotNull(info.Metadata);
Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder);
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
using var img = Image.Load(stream); using Image img = Image.Load(stream);
Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder);
}
} }
[Theory] [Theory]

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

@ -30,19 +30,17 @@ public class TiffDecoderTests : TiffDecoderBaseTester
[InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using MemoryStream stream = new(testFile.Bytes, false);
{ Image.TryIdentify(stream, out ImageInfo info);
ImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); Assert.Equal(expectedWidth, info.Width);
Assert.Equal(expectedWidth, info.Width); Assert.Equal(expectedHeight, info.Height);
Assert.Equal(expectedHeight, info.Height); Assert.NotNull(info.Metadata);
Assert.NotNull(info.Metadata); Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution);
Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution);
Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
}
} }
[Theory] [Theory]
@ -51,18 +49,16 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) public void ByteOrder(string imagePath, ByteOrder expectedByteOrder)
{ {
TestFile testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using MemoryStream stream = new(testFile.Bytes, false);
{ Image.TryIdentify(stream, out ImageInfo info);
ImageInfo info = Image.Identify(stream);
Assert.NotNull(info.Metadata); Assert.NotNull(info.Metadata);
Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder);
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
using Image img = Image.Load(stream); using Image img = Image.Load(stream);
Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder);
}
} }
[Theory] [Theory]

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

@ -27,12 +27,12 @@ public class TiffMetadataTests
[Fact] [Fact]
public void TiffMetadata_CloneIsDeep() public void TiffMetadata_CloneIsDeep()
{ {
var meta = new TiffMetadata TiffMetadata meta = new()
{ {
ByteOrder = ByteOrder.BigEndian, ByteOrder = ByteOrder.BigEndian,
}; };
var clone = (TiffMetadata)meta.DeepClone(); TiffMetadata clone = (TiffMetadata)meta.DeepClone();
clone.ByteOrder = ByteOrder.LittleEndian; clone.ByteOrder = ByteOrder.LittleEndian;
@ -46,10 +46,10 @@ public class TiffMetadataTests
{ {
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance); using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance);
TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); TiffFrameMetadata cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone();
VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData);
var clone = (TiffFrameMetadata)meta.DeepClone(); TiffFrameMetadata clone = (TiffFrameMetadata)meta.DeepClone();
clone.BitsPerPixel = TiffBitsPerPixel.Bit8; clone.BitsPerPixel = TiffBitsPerPixel.Bit8;
clone.Compression = TiffCompression.None; clone.Compression = TiffCompression.None;
@ -77,10 +77,10 @@ public class TiffMetadataTests
[InlineData(RgbUncompressed, 24)] [InlineData(RgbUncompressed, 24)]
public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata();
@ -93,10 +93,10 @@ public class TiffMetadataTests
[InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)]
public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata();
@ -173,7 +173,7 @@ public class TiffMetadataTests
Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value); Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value);
Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value);
Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value);
var expectedResolution = new Rational(10, 1, simplify: false); Rational expectedResolution = new(10, 1, simplify: false);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value);
Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value);
Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer());
@ -242,13 +242,13 @@ public class TiffMetadataTests
Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel);
// Save to Tiff // Save to Tiff
var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; TiffEncoder tiffEncoder = new() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb };
using var ms = new MemoryStream(); using MemoryStream ms = new();
image.Save(ms, tiffEncoder); image.Save(ms, tiffEncoder);
// Assert // Assert
ms.Position = 0; ms.Position = 0;
using var encodedImage = Image.Load<Rgba32>(ms); using Image<Rgba32> encodedImage = Image.Load<Rgba32>(ms);
ImageMetadata encodedImageMetaData = encodedImage.Metadata; ImageMetadata encodedImageMetaData = encodedImage.Metadata;
ImageFrame<Rgba32> rootFrameEncodedImage = encodedImage.Frames.RootFrame; ImageFrame<Rgba32> rootFrameEncodedImage = encodedImage.Frames.RootFrame;

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

@ -39,9 +39,9 @@ public class WebpDecoderTests
int expectedHeight, int expectedHeight,
int expectedBitsPerPixel) int expectedBitsPerPixel)
{ {
var testFile = TestFile.Create(imagePath); TestFile testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false); using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo imageInfo);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.Equal(expectedWidth, imageInfo.Width); Assert.Equal(expectedWidth, imageInfo.Width);
Assert.Equal(expectedHeight, imageInfo.Height); Assert.Equal(expectedHeight, imageInfo.Height);
@ -419,7 +419,7 @@ public class WebpDecoderTests
private static void RunDecodeLossyWithHorizontalFilter() private static void RunDecodeLossyWithHorizontalFilter()
{ {
var provider = TestImageProvider<Rgba32>.File(TestImageLossyHorizontalFilterPath); TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossyHorizontalFilterPath);
using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance); using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder); image.CompareToOriginal(provider, ReferenceDecoder);
@ -427,7 +427,7 @@ public class WebpDecoderTests
private static void RunDecodeLossyWithVerticalFilter() private static void RunDecodeLossyWithVerticalFilter()
{ {
var provider = TestImageProvider<Rgba32>.File(TestImageLossyVerticalFilterPath); TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossyVerticalFilterPath);
using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance); using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder); image.CompareToOriginal(provider, ReferenceDecoder);
@ -435,7 +435,7 @@ public class WebpDecoderTests
private static void RunDecodeLossyWithSimpleFilterTest() private static void RunDecodeLossyWithSimpleFilterTest()
{ {
var provider = TestImageProvider<Rgba32>.File(TestImageLossySimpleFilterPath); TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossySimpleFilterPath);
using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance); using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder); image.CompareToOriginal(provider, ReferenceDecoder);
@ -443,7 +443,7 @@ public class WebpDecoderTests
private static void RunDecodeLossyWithComplexFilterTest() private static void RunDecodeLossyWithComplexFilterTest()
{ {
var provider = TestImageProvider<Rgba32>.File(TestImageLossyComplexFilterPath); TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossyComplexFilterPath);
using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance); using Image<Rgba32> image = provider.GetImage(WebpDecoder.Instance);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, ReferenceDecoder); image.CompareToOriginal(provider, ReferenceDecoder);

2
tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs

@ -34,7 +34,7 @@ public partial class ImageTests
{ {
using FileStream fs = File.OpenRead(TestFile.GetInputFileFullPath(file)); using FileStream fs = File.OpenRead(TestFile.GetInputFileFullPath(file));
CancellationToken preCancelled = new(canceled: true); CancellationToken preCancelled = new(canceled: true);
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await Image.IdentifyAsync(fs, preCancelled)); await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await Image.TryIdentifyAsync(fs, preCancelled));
} }
private static TheoryData<bool, string, double> CreateLoadData() private static TheoryData<bool, string, double> CreateLoadData()

55
tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs

@ -16,9 +16,9 @@ public partial class ImageTests
{ {
private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F);
private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
private ReadOnlySpan<byte> ActualImageSpan => this.ActualImageBytes.AsSpan(); private static ReadOnlySpan<byte> ActualImageSpan => ActualImageBytes.AsSpan();
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
@ -34,7 +34,7 @@ public partial class ImageTests
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
{ {
bool result = Image.TryDetectFormat(this.ActualImageSpan, out IImageFormat type); bool result = Image.TryDetectFormat(ActualImageSpan, out IImageFormat type);
Assert.True(result); Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
@ -63,6 +63,15 @@ public partial class ImageTests
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
[Fact]
public async Task FromFileSystemPathAsync_GlobalConfiguration()
{
Attempt<IImageFormat> attempt = await Image.TryDetectFormatAsync(ActualImagePath);
Assert.True(attempt.Success);
Assert.Equal(ExpectedGlobalFormat, attempt.Value);
}
[Fact] [Fact]
public void FromFileSystemPath_CustomConfiguration() public void FromFileSystemPath_CustomConfiguration()
{ {
@ -78,15 +87,27 @@ public partial class ImageTests
} }
[Fact] [Fact]
public void FromStream_GlobalConfiguration() public async Task FromFileSystemPathAsync_CustomConfiguration()
{ {
using (var stream = new MemoryStream(this.ActualImageBytes)) DecoderOptions options = new()
{ {
bool result = Image.TryDetectFormat(stream, out IImageFormat type); Configuration = this.LocalConfiguration
};
Assert.True(result); Attempt<IImageFormat> attempt = await Image.TryDetectFormatAsync(options, this.MockFilePath);
Assert.Equal(ExpectedGlobalFormat, type);
} Assert.True(attempt.Success);
Assert.Equal(this.LocalImageFormat, attempt.Value);
}
[Fact]
public void FromStream_GlobalConfiguration()
{
using MemoryStream stream = new(ActualImageBytes);
bool result = Image.TryDetectFormat(stream, out IImageFormat type);
Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type);
} }
[Fact] [Fact]
@ -120,11 +141,9 @@ public partial class ImageTests
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration() public async Task FromStreamAsync_GlobalConfiguration()
{ {
using (var stream = new MemoryStream(this.ActualImageBytes)) using MemoryStream stream = new(ActualImageBytes);
{ Attempt<IImageFormat> attempt = await Image.TryDetectFormatAsync(new AsyncStreamWrapper(stream, () => false));
IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); Assert.Equal(ExpectedGlobalFormat, attempt.Value);
Assert.Equal(ExpectedGlobalFormat, type);
}
} }
[Fact] [Fact]
@ -135,8 +154,8 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); Attempt<IImageFormat> attempt = await Image.TryDetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false));
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, attempt.Value);
} }
[Fact] [Fact]
@ -147,8 +166,8 @@ public partial class ImageTests
Configuration = new() Configuration = new()
}; };
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); Attempt<IImageFormat> attempt = await Image.TryDetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false));
Assert.Null(type); Assert.Null(attempt.Value);
} }
} }
} }

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

@ -27,7 +27,7 @@ public partial class ImageTests
{ {
get get
{ {
Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out var format); Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out IImageFormat format);
return format!; return format!;
} }
} }
@ -35,30 +35,27 @@ public partial class ImageTests
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
{ {
ImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type); Image.TryIdentify(ActualImageBytes, out ImageInfo info);
Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedImageSize, info.Size());
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void FromBytes_CustomConfiguration() public void FromBytes_CustomConfiguration()
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
Image.TryIdentify(options, this.ByteArray, out ImageInfo info);
ImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void FromFileSystemPath_GlobalConfiguration() public void FromFileSystemPath_GlobalConfiguration()
{ {
ImageInfo info = Image.TryIdentify(ActualImagePath, out IImageFormat type); Image.TryIdentify(ActualImagePath, out ImageInfo info);
Assert.NotNull(info); Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
@ -66,27 +63,26 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
ImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); Image.TryIdentify(options, this.MockFilePath, out ImageInfo info);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void FromStream_GlobalConfiguration() public void FromStream_GlobalConfiguration()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
ImageInfo info = Image.Identify(stream, out IImageFormat type); Image.TryIdentify(stream, out ImageInfo info);
Assert.NotNull(info); Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void FromStream_GlobalConfiguration_NoFormat() public void FromStream_GlobalConfiguration_NoFormat()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
ImageInfo info = Image.Identify(stream); Image.TryIdentify(stream, out ImageInfo info);
Assert.NotNull(info); Assert.NotNull(info);
} }
@ -94,22 +90,22 @@ public partial class ImageTests
[Fact] [Fact]
public void FromNonSeekableStream_GlobalConfiguration() public void FromNonSeekableStream_GlobalConfiguration()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream); using NonSeekableStream nonSeekableStream = new(stream);
ImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type); Image.TryIdentify(nonSeekableStream, out ImageInfo info);
Assert.NotNull(info); Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void FromNonSeekableStream_GlobalConfiguration_NoFormat() public void FromNonSeekableStream_GlobalConfiguration_NoFormat()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream); using NonSeekableStream nonSeekableStream = new(stream);
ImageInfo info = Image.Identify(nonSeekableStream); Image.TryIdentify(nonSeekableStream, out ImageInfo info);
Assert.NotNull(info); Assert.NotNull(info);
} }
@ -119,10 +115,9 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
ImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); Image.TryIdentify(options, this.DataStream, out ImageInfo info);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
@ -130,7 +125,7 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
ImageInfo info = Image.TryIdentify(options, this.DataStream); Image.TryIdentify(options, this.DataStream, out ImageInfo info);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
} }
@ -140,17 +135,15 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = new() }; DecoderOptions options = new() { Configuration = new() };
ImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); Assert.False(Image.TryIdentify(options, this.DataStream, out ImageInfo info));
Assert.Null(info); Assert.Null(info);
Assert.Null(type);
} }
[Fact] [Fact]
public void FromStream_ZeroLength_ReturnsNull() public void FromStream_ZeroLength_ReturnsNull()
{ {
// https://github.com/SixLabors/ImageSharp/issues/1903 // https://github.com/SixLabors/ImageSharp/issues/1903
using var zipFile = new ZipArchive(new MemoryStream( using ZipArchive zipFile = new(new MemoryStream(
new byte[] new byte[]
{ {
0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00,
@ -165,61 +158,67 @@ public partial class ImageTests
0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00
})); }));
using Stream stream = zipFile.Entries[0].Open(); using Stream stream = zipFile.Entries[0].Open();
ImageInfo info = Image.Identify(stream);
Assert.False(Image.TryIdentify(stream, out ImageInfo info));
Assert.Null(info); Assert.Null(info);
} }
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration_NoFormat() public async Task FromStreamAsync_GlobalConfiguration_NoFormat()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false); AsyncStreamWrapper asyncStream = new(stream, () => false);
ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(asyncStream);
Assert.True(attempt.Success);
Assert.NotNull(attempt.Value);
} }
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration() public async Task FromStreamAsync_GlobalConfiguration()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
var asyncStream = new AsyncStreamWrapper(stream, () => false); AsyncStreamWrapper asyncStream = new(stream, () => false);
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.TryIdentifyAsync(asyncStream); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.True(attempt.Success);
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedImageSize, attempt.Value.Size());
Assert.Equal(ExpectedGlobalFormat, attempt.Value.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat() public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream); using NonSeekableStream nonSeekableStream = new(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); AsyncStreamWrapper asyncStream = new(nonSeekableStream, () => false);
ImageInfo info = await Image.IdentifyAsync(asyncStream); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(asyncStream);
Assert.NotNull(info); Assert.True(attempt.Success);
Assert.NotNull(attempt.Value);
} }
[Fact] [Fact]
public async Task FromNonSeekableStreamAsync_GlobalConfiguration() public async Task FromNonSeekableStreamAsync_GlobalConfiguration()
{ {
using var stream = new MemoryStream(ActualImageBytes); using MemoryStream stream = new(ActualImageBytes);
using var nonSeekableStream = new NonSeekableStream(stream); using NonSeekableStream nonSeekableStream = new(stream);
var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); AsyncStreamWrapper asyncStream = new(nonSeekableStream, () => false);
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.TryIdentifyAsync(asyncStream); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.True(attempt.Success);
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedImageSize, attempt.Value.Size());
Assert.Equal(ExpectedGlobalFormat, attempt.Value.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task FromStreamAsync_ZeroLength_ReturnsNull() public async Task FromStreamAsync_ZeroLength_ReturnsNull()
{ {
// https://github.com/SixLabors/ImageSharp/issues/1903 // https://github.com/SixLabors/ImageSharp/issues/1903
using var zipFile = new ZipArchive(new MemoryStream( using ZipArchive zipFile = new(new MemoryStream(
new byte[] new byte[]
{ {
0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00,
@ -234,8 +233,11 @@ public partial class ImageTests
0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00
})); }));
using Stream stream = zipFile.Entries[0].Open(); using Stream stream = zipFile.Entries[0].Open();
ImageInfo info = await Image.IdentifyAsync(stream);
Assert.Null(info); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(stream);
Assert.False(attempt.Success);
Assert.Null(attempt.Value);
} }
[Fact] [Fact]
@ -243,8 +245,10 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info);
Assert.True(attempt.Success);
Assert.Equal(this.LocalImageInfo, attempt.Value);
} }
[Fact] [Fact]
@ -252,27 +256,28 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
(ImageInfo ImageInfo, IImageFormat Format) info = Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(options, this.MockFilePath);
await Image.TryIdentifyAsync(options, this.MockFilePath);
Assert.NotNull(info.ImageInfo); Assert.True(attempt.Success);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.NotNull(attempt.Value);
} }
[Fact] [Fact]
public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration()
{ {
(ImageInfo ImageInfo, IImageFormat Format) res = await Image.TryIdentifyAsync(ActualImagePath); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.Equal(ExpectedImageSize, attempt.Value.Size());
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedGlobalFormat, attempt.Value.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task FromPathAsync_GlobalConfiguration() public async Task FromPathAsync_GlobalConfiguration()
{ {
ImageInfo info = await Image.IdentifyAsync(ActualImagePath); Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, info.Size()); Assert.True(attempt.Success);
Assert.Equal(ExpectedImageSize, attempt.Value.Size());
} }
[Fact] [Fact]
@ -280,12 +285,11 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); AsyncStreamWrapper asyncStream = new(this.DataStream, () => false);
(ImageInfo ImageInfo, IImageFormat Format) Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(options, asyncStream);
info = await Image.TryIdentifyAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.True(attempt.Success);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.Equal(this.LocalImageInfo, attempt.Value);
} }
[Fact] [Fact]
@ -293,11 +297,11 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = new() }; DecoderOptions options = new() { Configuration = new() };
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); AsyncStreamWrapper asyncStream = new(this.DataStream, () => false);
(ImageInfo ImageInfo, IImageFormat Format) Attempt<ImageInfo> attempt = await Image.TryIdentifyAsync(options, asyncStream);
info = await Image.TryIdentifyAsync(options, asyncStream);
Assert.Null(info.ImageInfo); Assert.False(attempt.Success);
Assert.Null(attempt.Value);
} }
} }
} }

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

@ -51,18 +51,22 @@ public partial class ImageTests
protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray();
protected ImageInfo LocalImageInfo => new( protected ImageInfo LocalImageInfo { get; }
this.localImageInfoMock.Object.PixelType,
this.localImageInfoMock.Object.Width,
this.localImageInfoMock.Object.Height,
this.localImageInfoMock.Object.Metadata);
protected ImageLoadTestBase() protected ImageLoadTestBase()
{ {
// TODO: Remove all this mocking. It's too complicated and we can now use fakes.
this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1); this.localStreamReturnImageRgba32 = new Image<Rgba32>(1, 1);
this.localStreamReturnImageAgnostic = new Image<Bgra4444>(1, 1); this.localStreamReturnImageAgnostic = new Image<Bgra4444>(1, 1);
this.localImageInfoMock = new Mock<IImageInfo>(); this.localImageInfoMock = new Mock<IImageInfo>();
this.LocalImageInfo = new(
this.localImageInfoMock.Object.PixelType,
this.localImageInfoMock.Object.Width,
this.localImageInfoMock.Object.Height,
this.localImageInfoMock.Object.Metadata);
this.localImageFormatMock = new Mock<IImageFormat>(); this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<IImageDecoder>(); this.localDecoder = new Mock<IImageDecoder>();

3
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -294,7 +294,6 @@ public partial class ImageTests
} }
} }
[Fact] [Fact]
public unsafe void WrapMemory_Throws_OnTooLessWrongSize() public unsafe void WrapMemory_Throws_OnTooLessWrongSize()
{ {
@ -307,7 +306,7 @@ public partial class ImageTests
{ {
try try
{ {
using (var image = Image.WrapMemory<Rgba32>(cfg, ptr, 24, 5, 5, metaData)); using var image = Image.WrapMemory<Rgba32>(cfg, ptr, 24, 5, 5, metaData);
} }
catch (Exception e) catch (Exception e)
{ {

8
tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -11,12 +11,10 @@ namespace SixLabors.ImageSharp.Tests;
/// </summary> /// </summary>
public class MockImageFormatDetector : IImageFormatDetector public class MockImageFormatDetector : IImageFormatDetector
{ {
private IImageFormat localImageFormatMock; private readonly IImageFormat localImageFormatMock;
public MockImageFormatDetector(IImageFormat imageFormat) public MockImageFormatDetector(IImageFormat imageFormat)
{ => this.localImageFormatMock = imageFormat;
this.localImageFormatMock = imageFormat;
}
public int HeaderSize => 1; public int HeaderSize => 1;

Loading…
Cancel
Save