Browse Source

Merge branch 'main' into icc-color-conversion

pull/1567/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
e3aa452402
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      shared-infrastructure
  2. 5
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  3. 11
      src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
  4. 8
      src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
  5. 7
      src/ImageSharp/Formats/IImageFormatDetector.cs
  6. 1
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  7. 22
      src/ImageSharp/Formats/ImageFormatManager.cs
  8. 10
      src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
  9. 9
      src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs
  10. 7
      src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
  11. 8
      src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
  12. 13
      src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
  13. 10
      src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs
  14. 3
      src/ImageSharp/Image.Decode.cs
  15. 26
      src/ImageSharp/Image.FromBytes.cs
  16. 15
      src/ImageSharp/Image.FromFile.cs
  17. 17
      src/ImageSharp/Image.FromStream.cs
  18. 4
      src/ImageSharp/Image{TPixel}.cs
  19. 3
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  20. 5
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs
  21. 3
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs
  22. 3
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs
  23. 3
      src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs
  24. 3
      src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs
  25. 7
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  26. 7
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  27. 5
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  28. 3
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs
  29. 10
      tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
  30. 6
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  31. 4
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  32. 36
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  33. 107
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  34. 4
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  35. 4
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  36. 7
      tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs
  37. 10
      tests/ImageSharp.Tests/TestFormat.cs
  38. 5
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

2
shared-infrastructure

@ -1 +1 @@
Subproject commit bf398a9b6bb6d0acfe0525ee109276b4ef74646c Subproject commit 9a82679e92df9476725fd2a2038604fd412af56c

5
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -27,8 +27,7 @@ public static class AdvancedImageExtensions
Guard.NotNull(filePath, nameof(filePath)); Guard.NotNull(filePath, nameof(filePath));
string ext = Path.GetExtension(filePath); string ext = Path.GetExtension(filePath);
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); if (!source.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format))
if (format is null)
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:");
@ -40,7 +39,7 @@ public static class AdvancedImageExtensions
throw new NotSupportedException(sb.ToString()); throw new NotSupportedException(sb.ToString());
} }
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); IImageEncoder? encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null) if (encoder is null)
{ {

11
src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs

@ -1,8 +1,8 @@
// 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 System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Bmp; namespace SixLabors.ImageSharp.Formats.Bmp;
@ -15,9 +15,11 @@ public sealed class BmpImageFormatDetector : IImageFormatDetector
public int HeaderSize => 2; public int HeaderSize => 2;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; format = this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
return format != null;
} }
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
@ -25,7 +27,8 @@ public sealed class BmpImageFormatDetector : IImageFormatDetector
if (header.Length >= this.HeaderSize) if (header.Length >= this.HeaderSize)
{ {
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header);
return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap ||
fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray;
} }
return false; return false;

8
src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Gif; namespace SixLabors.ImageSharp.Formats.Gif;
@ -13,9 +14,10 @@ public sealed class GifImageFormatDetector : IImageFormatDetector
public int HeaderSize => 6; public int HeaderSize => 6;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; format = this.IsSupportedFileFormat(header) ? GifFormat.Instance : null;
return format != null;
} }
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

7
src/ImageSharp/Formats/IImageFormatDetector.cs

@ -1,6 +1,8 @@
// 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;
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
/// <summary> /// <summary>
@ -18,6 +20,7 @@ public interface IImageFormatDetector
/// Detect mimetype /// Detect mimetype
/// </summary> /// </summary>
/// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param> /// <param name="header">The <see cref="T:byte[]"/> containing the file header.</param>
/// <returns>returns the mime type of detected otherwise returns null</returns> /// <param name="format">The mime type of detected otherwise returns null</param>
IImageFormat DetectFormat(ReadOnlySpan<byte> header); /// <returns>returns true when format was detected otherwise false.</returns>
bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format);
} }

1
src/ImageSharp/Formats/ImageDecoderUtilities.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.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;

22
src/ImageSharp/Formats/ImageFormatManager.cs

@ -1,8 +1,8 @@
// 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 System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
@ -92,8 +92,9 @@ public class ImageFormatManager
/// For the specified file extensions type find the e <see cref="IImageFormat"/>. /// For the specified file extensions type find the e <see cref="IImageFormat"/>.
/// </summary> /// </summary>
/// <param name="extension">The extension to discover</param> /// <param name="extension">The extension to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns> /// <param name="format">The <see cref="IImageFormat"/> if found otherwise null</param>
public IImageFormat FindFormatByFileExtension(string extension) /// <returns>False if no format was found</returns>
public bool TryFindFormatByFileExtension(string extension, [NotNullWhen(true)] out IImageFormat? format)
{ {
Guard.NotNullOrWhiteSpace(extension, nameof(extension)); Guard.NotNullOrWhiteSpace(extension, nameof(extension));
@ -102,7 +103,10 @@ public class ImageFormatManager
extension = extension[1..]; extension = extension[1..];
} }
return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); format = this.imageFormats.FirstOrDefault(x =>
x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
return format != null;
} }
/// <summary> /// <summary>
@ -110,7 +114,7 @@ public class ImageFormatManager
/// </summary> /// </summary>
/// <param name="mimeType">The mime-type to discover</param> /// <param name="mimeType">The mime-type to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found; otherwise null</returns> /// <returns>The <see cref="IImageFormat"/> if found; otherwise null</returns>
public IImageFormat FindFormatByMimeType(string mimeType) public IImageFormat? FindFormatByMimeType(string mimeType)
=> this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
/// <summary> /// <summary>
@ -160,11 +164,11 @@ public class ImageFormatManager
/// </summary> /// </summary>
/// <param name="format">The format to discover</param> /// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns> /// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
public IImageDecoder FindDecoder(IImageFormat format) public IImageDecoder? FindDecoder(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder) return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder? decoder)
? decoder ? decoder
: null; : null;
} }
@ -174,11 +178,11 @@ public class ImageFormatManager
/// </summary> /// </summary>
/// <param name="format">The format to discover</param> /// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns> /// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns>
public IImageEncoder FindEncoder(IImageFormat format) public IImageEncoder? FindEncoder(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder) return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder? encoder)
? encoder ? encoder
: null; : null;
} }

10
src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Jpeg; namespace SixLabors.ImageSharp.Formats.Jpeg;
@ -13,8 +14,11 @@ public sealed class JpegImageFormatDetector : IImageFormatDetector
public int HeaderSize => 11; public int HeaderSize => 11;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
=> this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; {
format = this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null;
return format != null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
=> header.Length >= this.HeaderSize => header.Length >= this.HeaderSize

9
src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Pbm; namespace SixLabors.ImageSharp.Formats.Pbm;
@ -17,7 +18,11 @@ public sealed class PbmImageFormatDetector : IImageFormatDetector
public int HeaderSize => 2; public int HeaderSize => 2;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) => IsSupportedFileFormat(header) ? PbmFormat.Instance : null; public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{
format = IsSupportedFileFormat(header) ? PbmFormat.Instance : null;
return format != null;
}
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{ {

7
src/ImageSharp/Formats/Png/PngImageFormatDetector.cs

@ -1,8 +1,8 @@
// 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 System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Png; namespace SixLabors.ImageSharp.Formats.Png;
@ -15,9 +15,10 @@ public sealed class PngImageFormatDetector : IImageFormatDetector
public int HeaderSize => 8; public int HeaderSize => 8;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; format = this.IsSupportedFileFormat(header) ? PngFormat.Instance : null;
return format != null;
} }
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

8
src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Tga; namespace SixLabors.ImageSharp.Formats.Tga;
@ -13,9 +14,10 @@ public sealed class TgaImageFormatDetector : IImageFormatDetector
public int HeaderSize => 16; public int HeaderSize => 16;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; format = this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null;
return format != null;
} }
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

13
src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Tiff; namespace SixLabors.ImageSharp.Formats.Tiff;
@ -13,14 +14,10 @@ public sealed class TiffImageFormatDetector : IImageFormatDetector
public int HeaderSize => 8; public int HeaderSize => 8;
/// <inheritdoc/> /// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
if (this.IsSupportedFileFormat(header)) format = this.IsSupportedFileFormat(header) ? TiffFormat.Instance : null;
{ return format != null;
return TiffFormat.Instance;
}
return null;
} }
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)

10
src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Webp; namespace SixLabors.ImageSharp.Formats.Webp;
@ -13,8 +14,11 @@ public sealed class WebpImageFormatDetector : IImageFormatDetector
public int HeaderSize => 12; public int HeaderSize => 12;
/// <inheritdoc /> /// <inheritdoc />
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
=> this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; {
format = this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null;
return format != null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
=> header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header); => header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header);

3
src/ImageSharp/Image.Decode.cs

@ -82,8 +82,7 @@ public abstract partial class Image
{ {
if (formatDetector.HeaderSize <= headerSize) if (formatDetector.HeaderSize <= headerSize)
{ {
IImageFormat attemptFormat = formatDetector.DetectFormat(headersBuffer); if (formatDetector.TryDetectFormat(headersBuffer, out IImageFormat attemptFormat))
if (attemptFormat != null)
{ {
format = attemptFormat; format = attemptFormat;
} }

26
src/ImageSharp/Image.FromBytes.cs

@ -1,7 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -16,18 +16,20 @@ public abstract partial class Image
/// By reading the header on the provided byte span this calculates the images format. /// By reading the header on the provided byte span this calculates the images format.
/// </summary> /// </summary>
/// <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>
/// <returns>The format or null if none found.</returns> /// <param name="format">The format or null if none found.</param>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) /// <returns>returns true when format was detected otherwise false.</returns>
=> DetectFormat(DecoderOptions.Default, data); public static bool TryDetectFormat(ReadOnlySpan<byte> data, [NotNullWhen(true)] out IImageFormat? format)
=> TryDetectFormat(DecoderOptions.Default, data, out format);
/// <summary> /// <summary>
/// By reading the header on the provided byte span this calculates the images format. /// By reading the header on the provided byte span this calculates the images format.
/// </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 mime type or null if none found.</param>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>returns true when format was detected otherwise false.</returns>
public static IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan<byte> data) public static bool TryDetectFormat(DecoderOptions options, ReadOnlySpan<byte> data, [NotNullWhen(true)] out IImageFormat? format)
{ {
Guard.NotNull(options, nameof(options.Configuration)); Guard.NotNull(options, nameof(options.Configuration));
@ -35,20 +37,20 @@ public abstract partial class Image
int maxHeaderSize = configuration.MaxHeaderSize; int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0) if (maxHeaderSize <= 0)
{ {
return null; format = null;
return false;
} }
foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors)
{ {
IImageFormat f = detector.DetectFormat(data); if (detector.TryDetectFormat(data, out format))
if (f != null)
{ {
return f; return true;
} }
} }
return default; format = default;
return false;
} }
/// <summary> /// <summary>

15
src/ImageSharp/Image.FromFile.cs

@ -1,6 +1,7 @@
// 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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -15,23 +16,25 @@ public abstract partial class Image
/// By reading the header on the provided file this calculates the images mime type. /// By reading the header on the provided file this calculates the images mime type.
/// </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>The mime type or null if none found.</returns> /// <param name="format">The mime type or null if none found.</param>
public static IImageFormat DetectFormat(string filePath) /// <returns>returns true when format was detected otherwise false.</returns>
=> DetectFormat(DecoderOptions.Default, filePath); public static bool TryDetectFormat(string filePath, [NotNullWhen(true)] out IImageFormat? format)
=> TryDetectFormat(DecoderOptions.Default, filePath, out format);
/// <summary> /// <summary>
/// By reading the header on the provided file this calculates the images mime type. /// By reading the header on the provided file this calculates the images mime type.
/// </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>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>returns true when format was detected otherwise false.</returns>
public static IImageFormat DetectFormat(DecoderOptions options, string filePath) public static bool TryDetectFormat(DecoderOptions options, string filePath, [NotNullWhen(true)] out IImageFormat? format)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
using Stream file = options.Configuration.FileSystem.OpenRead(filePath); using Stream file = options.Configuration.FileSystem.OpenRead(filePath);
return DetectFormat(options, file); return TryDetectFormat(options, file, out format);
} }
/// <summary> /// <summary>

17
src/ImageSharp/Image.FromStream.cs

@ -19,23 +19,28 @@ public abstract partial class Image
/// By reading the header on the provided stream this calculates the images format type. /// By reading the header on the provided stream this calculates the images format type.
/// </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>
/// <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>The format type or null if none found.</returns> /// <returns>returns true when format was detected otherwise false.</returns>
public static IImageFormat DetectFormat(Stream stream) public static bool TryDetectFormat(Stream stream, [NotNullWhen(true)] out IImageFormat? format)
=> DetectFormat(DecoderOptions.Default, stream); => TryDetectFormat(DecoderOptions.Default, stream, out format);
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// By reading the header on the provided stream this calculates the images format type.
/// </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>
/// <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>The format type or null if none found.</returns> /// <returns>returns true when format was detected otherwise false.</returns>
public static IImageFormat DetectFormat(DecoderOptions options, Stream stream) public static bool TryDetectFormat(DecoderOptions options, Stream stream, [NotNullWhen(true)] out IImageFormat? format)
=> WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); {
format = WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s));
return format != null;
}
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// By reading the header on the provided stream this calculates the images format type.

4
src/ImageSharp/Image{TPixel}.cs

@ -1,8 +1,6 @@
// 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 System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -420,7 +418,7 @@ public sealed class Image<TPixel> : Image
{ {
Guard.NotNull(frames, nameof(frames)); Guard.NotNull(frames, nameof(frames));
ImageFrame<TPixel> rootFrame = frames.FirstOrDefault(); ImageFrame<TPixel>? rootFrame = frames.FirstOrDefault();
if (rootFrame == null) if (rootFrame == null)
{ {

3
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.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.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -73,7 +72,7 @@ public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPi
// Create an interim clone of the source image to operate on. // Create an interim clone of the source image to operate on.
// Doing this allows for the application of transforms that will alter // Doing this allows for the application of transforms that will alter
// the dimensions of the image. // the dimensions of the image.
Image<TPixel> clone = default; Image<TPixel>? clone = default;
try try
{ {
clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute(); clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();

5
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -99,7 +100,7 @@ internal static class ConvolutionProcessorHelpers
/// <param name="row">The resulting 1D row vector, if possible.</param> /// <param name="row">The resulting 1D row vector, if possible.</param>
/// <param name="column">The resulting 1D column vector, if possible.</param> /// <param name="column">The resulting 1D column vector, if possible.</param>
/// <returns>Whether or not <paramref name="matrix"/> was linearly separable.</returns> /// <returns>Whether or not <paramref name="matrix"/> was linearly separable.</returns>
public static bool TryGetLinearlySeparableComponents(this DenseMatrix<float> matrix, out float[] row, out float[] column) public static bool TryGetLinearlySeparableComponents(this DenseMatrix<float> matrix, [NotNullWhen(true)] out float[]? row, [NotNullWhen(true)] out float[]? column)
{ {
int height = matrix.Rows; int height = matrix.Rows;
int width = matrix.Columns; int width = matrix.Columns;

3
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.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
namespace SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -88,7 +87,7 @@ public readonly struct EdgeDetector2DKernel : IEquatable<EdgeDetector2DKernel>
=> !(left == right); => !(left == right);
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object? obj)
=> obj is EdgeDetector2DKernel kernel && this.Equals(kernel); => obj is EdgeDetector2DKernel kernel && this.Equals(kernel);
/// <inheritdoc/> /// <inheritdoc/>

3
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.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
namespace SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -135,7 +134,7 @@ public readonly struct EdgeDetectorCompassKernel : IEquatable<EdgeDetectorCompas
=> !(left == right); => !(left == right);
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); public override bool Equals(object? obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel);
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast); public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast);

3
src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.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
namespace SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -64,7 +63,7 @@ public readonly struct EdgeDetectorKernel : IEquatable<EdgeDetectorKernel>
=> !(left == right); => !(left == right);
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object? obj)
=> obj is EdgeDetectorKernel kernel && this.Equals(kernel); => obj is EdgeDetectorKernel kernel && this.Equals(kernel);
/// <inheritdoc/> /// <inheritdoc/>

3
src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.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
namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
@ -37,7 +36,7 @@ internal readonly struct BokehBlurParameters : IEquatable<BokehBlurParameters>
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other); public override bool Equals(object? obj) => obj is BokehBlurParameters other && this.Equals(other);
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()

7
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.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 System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -212,7 +211,7 @@ public readonly partial struct ErrorDither : IDither, IEquatable<ErrorDither>, I
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object? obj)
=> obj is ErrorDither dither && this.Equals(dither); => obj is ErrorDither dither && this.Equals(dither);
/// <inheritdoc/> /// <inheritdoc/>
@ -220,8 +219,8 @@ public readonly partial struct ErrorDither : IDither, IEquatable<ErrorDither>, I
=> this.offset == other.offset && this.matrix.Equals(other.matrix); => this.offset == other.offset && this.matrix.Equals(other.matrix);
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(IDither other) public bool Equals(IDither? other)
=> this.Equals((object)other); => this.Equals((object?)other);
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()

7
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.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 System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -200,7 +199,7 @@ public readonly partial struct OrderedDither : IDither, IEquatable<OrderedDither
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object? obj)
=> obj is OrderedDither dither && this.Equals(dither); => obj is OrderedDither dither && this.Equals(dither);
/// <inheritdoc/> /// <inheritdoc/>
@ -209,8 +208,8 @@ public readonly partial struct OrderedDither : IDither, IEquatable<OrderedDither
=> this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; => this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY;
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(IDither other) public bool Equals(IDither? other)
=> this.Equals((object)other); => this.Equals((object?)other);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]

5
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.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 System.Buffers; using System.Buffers;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -19,7 +18,7 @@ internal sealed class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel>
{ {
private readonly DitherProcessor ditherProcessor; private readonly DitherProcessor ditherProcessor;
private readonly IDither dither; private readonly IDither dither;
private IMemoryOwner<TPixel> paletteOwner; private IMemoryOwner<TPixel>? paletteOwner;
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
@ -62,7 +61,7 @@ internal sealed class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel>
this.isDisposed = true; this.isDisposed = true;
if (disposing) if (disposing)
{ {
this.paletteOwner.Dispose(); this.paletteOwner?.Dispose();
this.ditherProcessor.Dispose(); this.ditherProcessor.Dispose();
} }

3
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs

@ -1,7 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -422,6 +422,7 @@ internal static class ResizeHelper
return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight)));
} }
[DoesNotReturn]
private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); private static void ThrowInvalid(string message) => throw new InvalidOperationException(message);
private static int Sanitize(int input) => Math.Max(1, input); private static int Sanitize(int input) => Math.Max(1, input);

10
tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs

@ -122,7 +122,13 @@ public class ImageFormatManagerTests
byte[] invalidImage = { 1, 2, 3 }; byte[] invalidImage = { 1, 2, 3 };
Assert.Equal(Image.DetectFormat(jpegImage), JpegFormat.Instance); bool resultValidImage = Image.TryDetectFormat(jpegImage, out IImageFormat format);
Assert.True(Image.DetectFormat(invalidImage) is null);
bool resultInvalidImage = Image.TryDetectFormat(invalidImage, out IImageFormat formatInvalid);
Assert.True(resultValidImage);
Assert.Equal(format, JpegFormat.Instance);
Assert.False(resultInvalidImage);
Assert.True(formatInvalid is null);
} }
} }

6
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs

@ -38,7 +38,7 @@ public abstract partial class ImageFrameCollectionTests
using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame((ImageFrame<Rgba32>)null); using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame((ImageFrame<Rgba32>)null);
}); });
Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); Assert.StartsWith("Value cannot be null. (Parameter 'frame')", ex.Message);
} }
[Fact] [Fact]
@ -52,7 +52,7 @@ public abstract partial class ImageFrameCollectionTests
using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame(data); using ImageFrame<Rgba32> addedFrame = this.Collection.AddFrame(data);
}); });
Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message);
} }
[Fact] [Fact]
@ -89,7 +89,7 @@ public abstract partial class ImageFrameCollectionTests
using ImageFrame<Rgba32> insertedFrame = this.Collection.InsertFrame(1, null); using ImageFrame<Rgba32> insertedFrame = this.Collection.InsertFrame(1, null);
}); });
Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); Assert.StartsWith("Value cannot be null. (Parameter 'frame')", ex.Message);
} }
[Fact] [Fact]

4
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

@ -77,7 +77,7 @@ public abstract partial class ImageFrameCollectionTests
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() => this.Collection.AddFrame(null)); () => this.Collection.AddFrame(null));
Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message);
} }
[Fact] [Fact]
@ -95,7 +95,7 @@ public abstract partial class ImageFrameCollectionTests
ArgumentNullException ex = Assert.Throws<ArgumentNullException>( ArgumentNullException ex = Assert.Throws<ArgumentNullException>(
() => this.Collection.InsertFrame(1, null)); () => this.Collection.InsertFrame(1, null));
Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); Assert.StartsWith("Value cannot be null. (Parameter 'source')", ex.Message);
} }
[Fact] [Fact]

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

@ -22,14 +22,21 @@ public partial class ImageTests
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
private static readonly IImageFormat ExpectedGlobalFormat = private static IImageFormat ExpectedGlobalFormat
Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); {
get
{
Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out IImageFormat format);
return format!;
}
}
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
{ {
IImageFormat type = Image.DetectFormat(this.ActualImageSpan); bool result = Image.TryDetectFormat(this.ActualImageSpan, out IImageFormat type);
Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
@ -41,15 +48,18 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
IImageFormat type = Image.DetectFormat(options, this.ByteArray); bool result = Image.TryDetectFormat(options, this.ByteArray, out IImageFormat type);
Assert.True(result);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void FromFileSystemPath_GlobalConfiguration() public void FromFileSystemPath_GlobalConfiguration()
{ {
IImageFormat type = Image.DetectFormat(ActualImagePath); bool result = Image.TryDetectFormat(ActualImagePath, out IImageFormat type);
Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
@ -61,7 +71,9 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
IImageFormat type = Image.DetectFormat(options, this.MockFilePath); bool result = Image.TryDetectFormat(options, this.MockFilePath, out IImageFormat type);
Assert.True(result);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
} }
@ -70,7 +82,9 @@ public partial class ImageTests
{ {
using (var stream = new MemoryStream(this.ActualImageBytes)) using (var stream = new MemoryStream(this.ActualImageBytes))
{ {
IImageFormat type = Image.DetectFormat(stream); bool result = Image.TryDetectFormat(stream, out IImageFormat type);
Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, type);
} }
} }
@ -83,7 +97,9 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
IImageFormat type = Image.DetectFormat(options, this.DataStream); bool result = Image.TryDetectFormat(options, this.DataStream, out IImageFormat type);
Assert.True(result);
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, type);
} }
@ -95,7 +111,9 @@ public partial class ImageTests
Configuration = new() Configuration = new()
}; };
IImageFormat type = Image.DetectFormat(options, this.DataStream); bool result = Image.TryDetectFormat(options, this.DataStream, out IImageFormat type);
Assert.False(result);
Assert.Null(type); Assert.Null(type);
} }

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

@ -25,8 +25,15 @@ public partial class ImageTests
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
private static readonly IImageFormat ExpectedGlobalFormat = private static IImageFormat ExpectedGlobalFormat
Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); {
get
{
Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out var format);
return format!;
}
}
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
@ -40,10 +47,7 @@ public partial class ImageTests
[Fact] [Fact]
public void FromBytes_CustomConfiguration() public void FromBytes_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type); IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type);
@ -63,10 +67,7 @@ public partial class ImageTests
[Fact] [Fact]
public void FromFileSystemPath_CustomConfiguration() public void FromFileSystemPath_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type);
@ -119,10 +120,7 @@ public partial class ImageTests
[Fact] [Fact]
public void FromStream_CustomConfiguration() public void FromStream_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
@ -133,10 +131,7 @@ public partial class ImageTests
[Fact] [Fact]
public void FromStream_CustomConfiguration_NoFormat() public void FromStream_CustomConfiguration_NoFormat()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
IImageInfo info = Image.Identify(options, this.DataStream); IImageInfo info = Image.Identify(options, this.DataStream);
@ -146,10 +141,7 @@ public partial class ImageTests
[Fact] [Fact]
public void WhenNoMatchingFormatFound_ReturnsNull() public void WhenNoMatchingFormatFound_ReturnsNull()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = new() };
{
Configuration = new()
};
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type);
@ -164,18 +156,15 @@ public partial class ImageTests
using var zipFile = new ZipArchive(new MemoryStream( using var zipFile = new ZipArchive(new MemoryStream(
new byte[] new byte[]
{ {
0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00,
0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x73, 0x74, 0x65, 0x72, 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00,
0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1,
0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B,
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00,
0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1,
0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00,
0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00
})); }));
using Stream stream = zipFile.Entries[0].Open(); using Stream stream = zipFile.Entries[0].Open();
@ -236,18 +225,15 @@ public partial class ImageTests
using var zipFile = new ZipArchive(new MemoryStream( using var zipFile = new ZipArchive(new MemoryStream(
new byte[] new byte[]
{ {
0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00,
0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x73, 0x74, 0x65, 0x72, 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00,
0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1,
0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B,
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00,
0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1,
0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00,
0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00
})); }));
using Stream stream = zipFile.Entries[0].Open(); using Stream stream = zipFile.Entries[0].Open();
@ -258,10 +244,7 @@ public partial class ImageTests
[Fact] [Fact]
public async Task FromPathAsync_CustomConfiguration() public async Task FromPathAsync_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
@ -270,12 +253,10 @@ public partial class ImageTests
[Fact] [Fact]
public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, this.MockFilePath); (IImageInfo ImageInfo, IImageFormat Format) info =
await Image.IdentifyWithFormatAsync(options, this.MockFilePath);
Assert.NotNull(info.ImageInfo); Assert.NotNull(info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.Equal(this.LocalImageFormat, info.Format);
} }
@ -300,13 +281,11 @@ public partial class ImageTests
[Fact] [Fact]
public async Task FromStreamAsync_CustomConfiguration() public async Task FromStreamAsync_CustomConfiguration()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = this.LocalConfiguration };
{
Configuration = this.LocalConfiguration
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); (IImageInfo ImageInfo, IImageFormat Format)
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.Equal(this.LocalImageInfo, info.ImageInfo);
Assert.Equal(this.LocalImageFormat, info.Format); Assert.Equal(this.LocalImageFormat, info.Format);
@ -315,13 +294,11 @@ public partial class ImageTests
[Fact] [Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull()
{ {
DecoderOptions options = new() DecoderOptions options = new() { Configuration = new() };
{
Configuration = new()
};
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); (IImageInfo ImageInfo, IImageFormat Format)
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Null(info.ImageInfo); Assert.Null(info.ImageInfo);
} }

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

@ -77,8 +77,8 @@ public partial class ImageTests
using (var image = new Image<Rgba32>(5, 5)) using (var image = new Image<Rgba32>(5, 5))
{ {
string ext = Path.GetExtension(filename); string ext = Path.GetExtension(filename);
IImageFormat format = image.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); image.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format);
Assert.Equal(mimeType, format.DefaultMimeType); Assert.Equal(mimeType, format!.DefaultMimeType);
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {

4
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -55,8 +55,8 @@ public class LargeImageIntegrationTests
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true; configuration.PreferContiguousImageBuffers = true;
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( configuration.ImageFormatsManager.TryFindFormatByFileExtension(formatInner, out IImageFormat format);
configuration.ImageFormatsManager.FindFormatByFileExtension(formatInner)); IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(format!);
string dir = TestEnvironment.CreateOutputDirectory(".Temp"); string dir = TestEnvironment.CreateOutputDirectory(".Temp");
string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}"); string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}");
using (Image<Rgba32> temp = new(2048, 2048)) using (Image<Rgba32> temp = new(2048, 2048))

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

@ -1,6 +1,7 @@
// 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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
namespace SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Tests;
@ -19,8 +20,10 @@ public class MockImageFormatDetector : IImageFormatDetector
public int HeaderSize => 1; public int HeaderSize => 1;
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
return this.localImageFormatMock; format = this.localImageFormatMock;
return true;
} }
} }

10
tests/ImageSharp.Tests/TestFormat.cs

@ -1,6 +1,7 @@
// 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.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -174,14 +175,11 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat
public int HeaderSize => this.testFormat.HeaderSize; public int HeaderSize => this.testFormat.HeaderSize;
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
if (this.testFormat.IsSupportedFileFormat(header)) format = this.testFormat.IsSupportedFileFormat(header) ? this.testFormat : null;
{
return this.testFormat;
}
return null; return format != null;
} }
public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; public TestHeader(TestFormat testFormat) => this.testFormat = testFormat;

5
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

@ -1,6 +1,7 @@
// 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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
@ -36,7 +37,9 @@ public static partial class TestEnvironment
{ {
string extension = Path.GetExtension(filePath); string extension = Path.GetExtension(filePath);
return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); Configuration.ImageFormatsManager.TryFindFormatByFileExtension(extension, out IImageFormat format);
return format;
} }
private static void ConfigureCodecs( private static void ConfigureCodecs(

Loading…
Cancel
Save