Browse Source

Merge branch 'main' into icc-color-conversion

pull/1567/head
James Jackson-South 3 years ago
parent
commit
0dd68fedca
  1. 18
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  3. 2
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 18
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  5. 2
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  6. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  7. 4
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  8. 6
      src/ImageSharp/Formats/IImageDecoder.cs
  9. 4
      src/ImageSharp/Formats/IImageDecoderInternals.cs
  10. 67
      src/ImageSharp/Formats/ImageDecoder.cs
  11. 2
      src/ImageSharp/Formats/ImageDecoderUtilities.cs
  12. 64
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  13. 78
      src/ImageSharp/Formats/ImageFormatManager.cs
  14. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  15. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  16. 2
      src/ImageSharp/Formats/Pbm/PbmDecoder.cs
  17. 2
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  18. 2
      src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs
  19. 15
      src/ImageSharp/Formats/PixelTypeInfo.cs
  20. 4
      src/ImageSharp/Formats/Png/PngDecoder.cs
  21. 2
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  22. 2
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  23. 14
      src/ImageSharp/Formats/SpecializedImageDecoder{T}.cs
  24. 2
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  25. 2
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  26. 2
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  27. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  28. 2
      src/ImageSharp/Formats/Webp/WebpDecoder.cs
  29. 2
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  30. 11
      src/ImageSharp/IImage.cs
  31. 34
      src/ImageSharp/IImageInfo.cs
  32. 108
      src/ImageSharp/Image.Decode.cs
  33. 233
      src/ImageSharp/Image.FromBytes.cs
  34. 375
      src/ImageSharp/Image.FromFile.cs
  35. 474
      src/ImageSharp/Image.FromStream.cs
  36. 2
      src/ImageSharp/Image.LoadPixelData.cs
  37. 32
      src/ImageSharp/Image.cs
  38. 4
      src/ImageSharp/ImageExtensions.cs
  39. 58
      src/ImageSharp/ImageInfo.cs
  40. 24
      src/ImageSharp/ImageInfoExtensions.cs
  41. 2
      src/ImageSharp/Image{TPixel}.cs
  42. 20
      src/ImageSharp/Metadata/ImageMetadata.cs
  43. 2
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  44. 2
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  45. 38
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  46. 2
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs
  47. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  48. 2
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs
  49. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  50. 4
      tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs
  51. 17
      tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs
  52. 16
      tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs
  53. 8
      tests/ImageSharp.Benchmarks/Processing/Diffuse.cs
  54. 4
      tests/ImageSharp.Benchmarks/Processing/Rotate.cs
  55. 4
      tests/ImageSharp.Benchmarks/Processing/Skew.cs
  56. 12
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  57. 16
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  58. 33
      tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs
  59. 72
      tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs
  60. 19
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  61. 18
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  62. 6
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  63. 72
      tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs
  64. 21
      tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
  65. 72
      tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs
  66. 54
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  67. 10
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  68. 72
      tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs
  69. 23
      tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs
  70. 72
      tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs
  71. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  72. 48
      tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs
  73. 72
      tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs
  74. 8
      tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
  75. 50
      tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs
  76. 72
      tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs
  77. 40
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  78. 28
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  79. 72
      tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs
  80. 14
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  81. 12
      tests/ImageSharp.Tests/Image/ImageRotationTests.cs
  82. 82
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  83. 150
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  84. 28
      tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs
  85. 38
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs
  86. 15
      tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs
  87. 36
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs
  88. 15
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs
  89. 54
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs
  90. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs
  91. 31
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs
  92. 34
      tests/ImageSharp.Tests/Image/ImageTests.Save.cs
  93. 88
      tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs
  94. 3
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  95. 72
      tests/ImageSharp.Tests/Image/ImageTests.cs
  96. 2
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  97. 8
      tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs
  98. 24
      tests/ImageSharp.Tests/ImageInfoTests.cs
  99. 2
      tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
  100. 2
      tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs

18
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -19,9 +19,9 @@ public static class AdvancedImageExtensions
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="filePath">The target file path to save the image to.</param> /// <param name="filePath">The target file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The file path is null.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided path.</exception>
/// <returns>The matching <see cref="IImageEncoder"/>.</returns> /// <returns>The matching <see cref="IImageEncoder"/>.</returns>
/// <exception cref="ArgumentNullException">The file path is null.</exception>
/// <exception cref="UnknownImageFormatException">No encoder available for provided path.</exception>
public static IImageEncoder DetectEncoder(this Image source, string filePath) public static IImageEncoder DetectEncoder(this Image source, string filePath)
{ {
Guard.NotNull(filePath, nameof(filePath)); Guard.NotNull(filePath, nameof(filePath));
@ -30,27 +30,27 @@ public static class AdvancedImageExtensions
if (!source.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format)) if (!source.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format))
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
{ {
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
} }
throw new NotSupportedException(sb.ToString()); throw new UnknownImageFormatException(sb.ToString());
} }
IImageEncoder? encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); IImageEncoder? encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{ {
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
} }
throw new NotSupportedException(sb.ToString()); throw new UnknownImageFormatException(sb.ToString());
} }
return encoder; return encoder;

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

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

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

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

18
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -215,15 +215,15 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
BmpInfoHeader infoHeader = new( BmpInfoHeader infoHeader = new(
headerSize: infoHeaderSize, headerSize: infoHeaderSize,
height: height,
width: width, width: width,
bitsPerPixel: bpp, height: height,
planes: 1, planes: 1,
bitsPerPixel: bpp,
imageSize: height * bytesPerLine, imageSize: height * bytesPerLine,
clrUsed: 0,
clrImportant: 0,
xPelsPerMeter: hResolution, xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution); yPelsPerMeter: vResolution,
clrUsed: 0,
clrImportant: 0);
if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
{ {
@ -470,7 +470,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
@ -541,7 +541,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@ -588,7 +588,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
@ -644,7 +644,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean); using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); Span<byte> colorPalette = colorPaletteBuffer.GetSpan();

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

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

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

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

4
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -99,12 +99,12 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
if (useGlobalTable) if (useGlobalTable)
{ {
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
} }
else else
{ {
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image.Frames.RootFrame); frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image.Frames.RootFrame);
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
} }
} }

6
src/ImageSharp/Formats/IImageDecoder.cs

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

4
src/ImageSharp/Formats/IImageDecoderInternals.cs

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

67
src/ImageSharp/Formats/ImageDecoder.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.ColorSpaces.Conversion.Icc; using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc;
using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.Icc; using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.Icc;
@ -27,6 +26,8 @@ public abstract class ImageDecoder : IImageDecoder
s => this.Decode<TPixel>(options, s, default)); s => this.Decode<TPixel>(options, s, default));
TransformColorProfile(options, image); TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image; return image;
} }
@ -39,6 +40,8 @@ public abstract class ImageDecoder : IImageDecoder
s => this.Decode(options, s, default)); s => this.Decode(options, s, default));
TransformColorProfile(options, image); TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image; return image;
} }
@ -53,6 +56,8 @@ public abstract class ImageDecoder : IImageDecoder
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
TransformColorProfile(options, image); TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image; return image;
} }
@ -66,23 +71,37 @@ public abstract class ImageDecoder : IImageDecoder
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
TransformColorProfile(options, image); TransformColorProfile(options, image);
this.SetDecoderFormat(options.Configuration, image);
return image; return image;
} }
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(DecoderOptions options, Stream stream) public ImageInfo Identify(DecoderOptions options, Stream stream)
=> WithSeekableStream( {
options, ImageInfo info = WithSeekableStream(
stream, options,
s => this.Identify(options, s, default)); stream,
s => this.Identify(options, s, default));
this.SetDecoderFormat(options.Configuration, info);
return info;
}
/// <inheritdoc/> /// <inheritdoc/>
public Task<IImageInfo> 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.
@ -121,9 +140,9 @@ public abstract class ImageDecoder : IImageDecoder
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The <see cref="IImageInfo"/> object.</returns> /// <returns>The <see cref="ImageInfo"/> object.</returns>
/// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception> /// <exception cref="ImageFormatException">Thrown if the encoded image contains errors.</exception>
protected abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); protected abstract ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Performs a scaling operation against the decoded image. If the target size is not set, or the image size /// Performs a scaling operation against the decoded image. If the target size is not set, or the image size
@ -137,7 +156,7 @@ public abstract class ImageDecoder : IImageDecoder
{ {
ResizeOptions resizeOptions = new() ResizeOptions resizeOptions = new()
{ {
Size = options.TargetSize.Value, Size = options.TargetSize!.Value,
Sampler = options.Sampler, Sampler = options.Sampler,
Mode = ResizeMode.Max Mode = ResizeMode.Max
}; };
@ -179,7 +198,7 @@ public abstract class ImageDecoder : IImageDecoder
} }
Size targetSize = options.TargetSize.Value; Size targetSize = options.TargetSize.Value;
Size currentSize = image.Size(); Size currentSize = image.Size;
return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height;
} }
@ -294,4 +313,20 @@ public abstract class ImageDecoder : IImageDecoder
memoryStream.Position = 0; memoryStream.Position = 0;
return await action(memoryStream, position, cancellationToken).ConfigureAwait(false); return await action(memoryStream, position, cancellationToken).ConfigureAwait(false);
} }
internal void SetDecoderFormat(Configuration configuration, Image image)
{
if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
{
image.Metadata.DecodedImageFormat = format;
}
}
internal void SetDecoderFormat(Configuration configuration, ImageInfo info)
{
if (configuration.ImageFormatsManager.TryFindFormatByDecoder(this, out IImageFormat? format))
{
info.Metadata.DecodedImageFormat = format;
}
}
} }

2
src/ImageSharp/Formats/ImageDecoderUtilities.cs

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

64
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -58,7 +58,7 @@ public static partial class ImageExtensions
public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Bmp format. /// Saves the image to the given stream with the Bmp format.
@ -72,7 +72,7 @@ public static partial class ImageExtensions
public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -105,7 +105,7 @@ public static partial class ImageExtensions
public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Bmp format. /// Saves the image to the given stream with the Bmp format.
@ -119,7 +119,7 @@ public static partial class ImageExtensions
public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(BmpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -160,7 +160,7 @@ public static partial class ImageExtensions
public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => public static void SaveAsGif(this Image source, string path, GifEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Gif format. /// Saves the image to the given stream with the Gif format.
@ -174,7 +174,7 @@ public static partial class ImageExtensions
public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -207,7 +207,7 @@ public static partial class ImageExtensions
public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Gif format. /// Saves the image to the given stream with the Gif format.
@ -221,7 +221,7 @@ public static partial class ImageExtensions
public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(GifFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -262,7 +262,7 @@ public static partial class ImageExtensions
public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Jpeg format. /// Saves the image to the given stream with the Jpeg format.
@ -276,7 +276,7 @@ public static partial class ImageExtensions
public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -309,7 +309,7 @@ public static partial class ImageExtensions
public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Jpeg format. /// Saves the image to the given stream with the Jpeg format.
@ -323,7 +323,7 @@ public static partial class ImageExtensions
public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(JpegFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -364,7 +364,7 @@ public static partial class ImageExtensions
public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Pbm format. /// Saves the image to the given stream with the Pbm format.
@ -378,7 +378,7 @@ public static partial class ImageExtensions
public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -411,7 +411,7 @@ public static partial class ImageExtensions
public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Pbm format. /// Saves the image to the given stream with the Pbm format.
@ -425,7 +425,7 @@ public static partial class ImageExtensions
public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PbmFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -466,7 +466,7 @@ public static partial class ImageExtensions
public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => public static void SaveAsPng(this Image source, string path, PngEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Png format. /// Saves the image to the given stream with the Png format.
@ -480,7 +480,7 @@ public static partial class ImageExtensions
public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -513,7 +513,7 @@ public static partial class ImageExtensions
public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Png format. /// Saves the image to the given stream with the Png format.
@ -527,7 +527,7 @@ public static partial class ImageExtensions
public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(PngFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -568,7 +568,7 @@ public static partial class ImageExtensions
public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tga format. /// Saves the image to the given stream with the Tga format.
@ -582,7 +582,7 @@ public static partial class ImageExtensions
public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -615,7 +615,7 @@ public static partial class ImageExtensions
public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tga format. /// Saves the image to the given stream with the Tga format.
@ -629,7 +629,7 @@ public static partial class ImageExtensions
public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -670,7 +670,7 @@ public static partial class ImageExtensions
public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Webp format. /// Saves the image to the given stream with the Webp format.
@ -684,7 +684,7 @@ public static partial class ImageExtensions
public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -717,7 +717,7 @@ public static partial class ImageExtensions
public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Webp format. /// Saves the image to the given stream with the Webp format.
@ -731,7 +731,7 @@ public static partial class ImageExtensions
public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(WebpFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -772,7 +772,7 @@ public static partial class ImageExtensions
public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
source.Save( source.Save(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tiff format. /// Saves the image to the given stream with the Tiff format.
@ -786,7 +786,7 @@ public static partial class ImageExtensions
public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
path, path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken); cancellationToken);
/// <summary> /// <summary>
@ -819,7 +819,7 @@ public static partial class ImageExtensions
public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
=> source.Save( => source.Save(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance));
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tiff format. /// Saves the image to the given stream with the Tiff format.
@ -833,7 +833,7 @@ public static partial class ImageExtensions
public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default)
=> source.SaveAsync( => source.SaveAsync(
stream, stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.GetEncoder(TiffFormat.Instance),
cancellationToken); cancellationToken);
} }

78
src/ImageSharp/Formats/ImageFormatManager.cs

@ -3,6 +3,8 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
namespace SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.Formats;
@ -91,9 +93,13 @@ public class ImageFormatManager
/// <summary> /// <summary>
/// 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 return the format for.</param>
/// <param name="format">The <see cref="IImageFormat"/> if found otherwise null</param> /// <param name="format">
/// <returns>False if no format was found</returns> /// When this method returns, contains the format that matches the given extension;
/// 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 bool TryFindFormatByFileExtension(string extension, [NotNullWhen(true)] out IImageFormat? format) public bool TryFindFormatByFileExtension(string extension, [NotNullWhen(true)] out IImageFormat? format)
{ {
Guard.NotNullOrWhiteSpace(extension, nameof(extension)); Guard.NotNullOrWhiteSpace(extension, nameof(extension));
@ -106,16 +112,30 @@ public class ImageFormatManager
format = this.imageFormats.FirstOrDefault(x => format = this.imageFormats.FirstOrDefault(x =>
x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
return format != null; return format is not null;
} }
/// <summary> /// <summary>
/// For the specified mime type find the <see cref="IImageFormat"/>. /// For the specified mime type find the <see cref="IImageFormat"/>.
/// </summary> /// </summary>
/// <param name="mimeType">The mime-type to discover</param> /// <param name="mimeType">The mime-type to return the format for.</param>
/// <returns>The <see cref="IImageFormat"/> if found; otherwise null</returns> /// <param name="format">
public IImageFormat? FindFormatByMimeType(string mimeType) /// When this method returns, contains the format that matches the given mime-type;
=> this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); /// 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 bool TryFindFormatByMimeType(string mimeType, [NotNullWhen(true)] out IImageFormat? format)
{
format = this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
return format is not null;
}
internal bool TryFindFormatByDecoder(IImageDecoder decoder, [NotNullWhen(true)] out IImageFormat? format)
{
format = this.mimeTypeDecoders.FirstOrDefault(x => x.Value.GetType() == decoder.GetType()).Key;
return format is not null;
}
/// <summary> /// <summary>
/// Sets a specific image encoder as the encoder for a specific image format. /// Sets a specific image encoder as the encoder for a specific image format.
@ -163,32 +183,54 @@ public class ImageFormatManager
/// For the specified mime type find the decoder. /// For the specified mime type find the decoder.
/// </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"/>.</returns>
public IImageDecoder? FindDecoder(IImageFormat format) /// <exception cref="UnknownImageFormatException">The format is not registered.</exception>
public IImageDecoder GetDecoder(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder? decoder) if (!this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder? decoder))
? decoder {
: null; ThrowInvalidDecoder(this);
}
return decoder;
} }
/// <summary> /// <summary>
/// For the specified mime type find the encoder. /// For the specified mime type find the encoder.
/// </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"/>.</returns>
public IImageEncoder? FindEncoder(IImageFormat format) /// <exception cref="UnknownImageFormatException">The format is not registered.</exception>
public IImageEncoder GetEncoder(IImageFormat format)
{ {
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder? encoder) if (!this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder? encoder))
? encoder {
: null; ThrowInvalidDecoder(this);
}
return encoder;
} }
/// <summary> /// <summary>
/// Sets the max header size. /// Sets the max header size.
/// </summary> /// </summary>
private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize);
[DoesNotReturn]
internal static void ThrowInvalidDecoder(ImageFormatManager manager)
{
StringBuilder sb = new();
sb = sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in manager.ImageDecoders)
{
sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
}
} }

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

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

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

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

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

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

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

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

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

@ -68,7 +68,7 @@ internal sealed class PbmEncoderCore : IImageEncoderInternals
this.SanitizeAndSetEncoderOptions(image); this.SanitizeAndSetEncoderOptions(image);
byte signature = this.DeduceSignature(); byte signature = this.DeduceSignature();
this.WriteHeader(stream, signature, image.Size()); this.WriteHeader(stream, signature, image.Size);
this.WritePixels(stream, image.Frames.RootFrame); this.WritePixels(stream, image.Frames.RootFrame);

15
src/ImageSharp/Formats/PixelTypeInfo.cs

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

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

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

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

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

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

@ -1300,7 +1300,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration()); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration());
frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image); frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, image);
return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); return frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
} }
/// <summary> /// <summary>

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

@ -19,11 +19,13 @@ public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedIma
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Image<TPixel> image = WithSeekableStream( Image<TPixel> image = WithSeekableStream(
options.GeneralOptions, options.GeneralOptions,
stream, stream,
s => this.Decode<TPixel>(options, s, default)); s => this.Decode<TPixel>(options, s, default));
TransformColorProfile(options.GeneralOptions, image); TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image; return image;
} }
@ -36,6 +38,8 @@ public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedIma
s => this.Decode(options, s, default)); s => this.Decode(options, s, default));
TransformColorProfile(options.GeneralOptions, image); TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image; return image;
} }
@ -50,6 +54,8 @@ public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedIma
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
TransformColorProfile(options.GeneralOptions, image); TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image; return image;
} }
@ -63,6 +69,8 @@ public abstract class SpecializedImageDecoder<T> : ImageDecoder, ISpecializedIma
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
TransformColorProfile(options.GeneralOptions, image); TransformColorProfile(options.GeneralOptions, image);
this.SetDecoderFormat(options.GeneralOptions.Configuration, image);
return image; return image;
} }

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

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

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

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

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

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

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

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

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

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

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

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

11
src/ImageSharp/IImage.cs

@ -1,11 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp;
/// <summary>
/// Encapsulates the properties and methods that describe an image.
/// </summary>
public interface IImage : IImageInfo, IDisposable
{
}

34
src/ImageSharp/IImageInfo.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp;
/// <summary>
/// Encapsulates properties that describe basic image information including dimensions, pixel type information
/// and additional metadata.
/// </summary>
public interface IImageInfo
{
/// <summary>
/// Gets information about the image pixels.
/// </summary>
PixelTypeInfo PixelType { get; }
/// <summary>
/// Gets the width.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the metadata of the image.
/// </summary>
ImageMetadata Metadata { get; }
}

108
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,6 +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>
/// <exception cref="UnknownImageFormatException">The input format is not recognized.</exception>
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
@ -52,7 +52,7 @@ public abstract partial class Image
int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length);
if (headerSize <= 0) if (headerSize <= 0)
{ {
return null; ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager);
} }
// Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice, // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice,
@ -77,19 +77,21 @@ 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;
}
} }
} }
return format; if (format is null)
{
ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager);
}
return format!;
} }
/// <summary> /// <summary>
@ -97,15 +99,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"/>.</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 options.Configuration.ImageFormatsManager.GetDecoder(format);
return format != null
? options.Configuration.ImageFormatsManager.FindDecoder(format)
: null;
} }
/// <summary> /// <summary>
@ -117,60 +115,36 @@ 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) return decoder.Decode<TPixel>(options, stream);
{
return (null, null);
}
Image<TPixel> img = decoder.Decode<TPixel>(options, stream);
return (img, format);
} }
private static async Task<(Image<TPixel> Image, IImageFormat Format)> DecodeAsync<TPixel>( private static 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) return decoder.DecodeAsync<TPixel>(options, stream, cancellationToken);
{
return (null, null);
}
Image<TPixel> img = 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) return decoder.Decode(options, stream);
{
return (null, null);
}
Image img = decoder.Decode(options, stream);
return (img, format);
} }
private static async Task<(Image Image, IImageFormat Format)> DecodeAsync( private static 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) return decoder.DecodeAsync(options, stream, cancellationToken);
{
return (null, null);
}
Image img = await decoder.DecodeAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (img, format);
} }
/// <summary> /// <summary>
@ -178,14 +152,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 stream.</param> /// <param name="stream">The stream.</param>
/// <returns> /// <returns>The <see cref="ImageInfo"/>.</returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. private static ImageInfo InternalIdentify(DecoderOptions options, Stream stream)
/// </returns>
private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentify(DecoderOptions options, Stream stream)
{ {
IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); IImageDecoder decoder = DiscoverDecoder(options, stream);
IImageInfo info = decoder?.Identify(options, stream); return decoder.Identify(options, stream);
return (info, format);
} }
/// <summary> /// <summary>
@ -194,22 +165,13 @@ public abstract partial class Image
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns> /// <returns>The <see cref="ImageInfo"/>.</returns>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. private static Task<ImageInfo> InternalIdentifyAsync(
/// </returns>
private static async Task<(IImageInfo ImageInfo, IImageFormat Format)> 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);
return decoder.IdentifyAsync(options, stream, cancellationToken);
if (decoder is null)
{
return (null, null);
}
IImageInfo info = await decoder.IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
return (info, format);
} }
} }

233
src/ImageSharp/Image.FromBytes.cs

@ -1,7 +1,6 @@
// 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,215 +14,129 @@ public abstract partial class Image
/// <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="data">The byte span containing encoded image data to read the header from.</param> /// <param name="buffer">The byte span containing encoded image data to read the header from.</param>
/// <param name="format">The format or null if none found.</param> /// <returns>The <see cref="IImageFormat"/>.</returns>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
public static bool TryDetectFormat(ReadOnlySpan<byte> data, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
=> TryDetectFormat(DecoderOptions.Default, data, out format); /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> buffer)
=> DetectFormat(DecoderOptions.Default, buffer);
/// <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="buffer">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> /// <returns>The <see cref="IImageFormat"/>.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
public static bool TryDetectFormat(DecoderOptions options, ReadOnlySpan<byte> data, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static unsafe IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan<byte> buffer)
{ {
Guard.NotNull(options, nameof(options.Configuration)); Guard.NotNull(options, nameof(options.Configuration));
Configuration configuration = options.Configuration; fixed (byte* ptr = buffer)
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{ {
format = null; using UnmanagedMemoryStream stream = new(ptr, buffer.Length);
return false; return DetectFormat(options, stream);
} }
foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors)
{
if (detector.TryDetectFormat(data, out format))
{
return true;
}
}
format = default;
return false;
} }
/// <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> /// </summary>
/// <param name="data">The byte span containing encoded image data to read the header from.</param> /// <param name="buffer">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <returns>The <see cref="ImageInfo"/>.</returns>
/// <exception cref="NotSupportedException">The data is not readable.</exception> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// </returns> public static ImageInfo Identify(ReadOnlySpan<byte> buffer)
public static IImageInfo Identify(ReadOnlySpan<byte> data) => Identify(data, out IImageFormat _); => Identify(DecoderOptions.Default, buffer);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo 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.
/// </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="buffer">The byte span containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <returns>The <see cref="ImageInfo"/>.</returns>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> buffer)
/// </returns>
public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> data, out IImageFormat format)
{ {
fixed (byte* ptr = data) fixed (byte* ptr = buffer)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, buffer.Length);
return Identify(options, stream, out format); return Identify(options, stream);
} }
} }
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Creates a new instance of the <see cref="Image"/> class from the given byte span.
/// </summary> /// The pixel format is automatically determined by the decoder.
/// <param name="data">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="data">The byte span containing image data.</param> /// <param name="buffer">The byte span containing encoded image data.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <returns><see cref="Image"/>.</returns>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <returns>The <see cref="Image"/>.</returns>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> public static Image Load(ReadOnlySpan<byte> buffer)
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format) => Load(DecoderOptions.Default, buffer);
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, data, out format);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Creates a new instance of the <see cref="Image"/> class from the given byte span.
/// The pixel format is automatically determined by the decoder.
/// </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.</param> /// <param name="buffer">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <returns><see cref="Image"/>.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> public static unsafe Image Load(DecoderOptions options, ReadOnlySpan<byte> buffer)
public static unsafe Image<TPixel> Load<TPixel>(DecoderOptions options, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
{ {
fixed (byte* ptr = data) fixed (byte* ptr = buffer)
{ {
using var stream = new UnmanagedMemoryStream(ptr, data.Length); using UnmanagedMemoryStream stream = new(ptr, buffer.Length);
return Load<TPixel>(options, stream); return Load(options, stream);
} }
} }
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given byte span.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <param name="data">The byte span containing encoded image data.</param>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <returns><see cref="Image{TPixel}"/>.</returns>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static unsafe Image<TPixel> Load<TPixel>( public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
DecoderOptions options,
ReadOnlySpan<byte> data,
out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Load<TPixel>(DecoderOptions.Default, data);
fixed (byte* ptr = data)
{
using var stream = new UnmanagedMemoryStream(ptr, data.Length);
return Load<TPixel>(options, stream, out format);
}
}
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="data">The byte span containing image data.</param>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data)
=> Load(DecoderOptions.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The detected format.</param>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
=> Load(DecoderOptions.Default, data, out format);
/// <summary>
/// Decodes a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(DecoderOptions options, ReadOnlySpan<byte> data)
=> Load(options, data, out _);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given byte span.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing encoded image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>> /// <returns><see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>The <see cref="Image"/>.</returns> public static unsafe Image<TPixel> Load<TPixel>(DecoderOptions options, ReadOnlySpan<byte> data)
public static unsafe Image Load( where TPixel : unmanaged, IPixel<TPixel>
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 Load(options, stream, out format); return Load<TPixel>(options, stream);
} }
} }
} }

375
src/ImageSharp/Image.FromFile.cs

@ -1,7 +1,6 @@
// 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;
@ -13,298 +12,238 @@ 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.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="path">The image file to open and to read the header from.</param>
/// <param name="format">The mime type or null if none found.</param> /// <returns>The <see cref="IImageFormat"/>.</returns>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="ArgumentNullException">The path is null.</exception>
public static bool TryDetectFormat(string filePath, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
=> TryDetectFormat(DecoderOptions.Default, filePath, out format); /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static IImageFormat DetectFormat(string path)
=> DetectFormat(DecoderOptions.Default, path);
/// <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.
/// </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="path">The image file to open and to read the header from.</param>
/// <param name="format">The mime type or null if none found.</param> /// <returns>The <see cref="IImageFormat"/>.</returns>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="ArgumentNullException">The path is null.</exception>
public static bool TryDetectFormat(DecoderOptions options, string filePath, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static IImageFormat DetectFormat(DecoderOptions options, string path)
{ {
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(path);
return TryDetectFormat(options, file, out format); return DetectFormat(options, file);
} }
/// <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.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="path">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="IImageInfo"/> or null if suitable info detector not found. /// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</returns>
/// </returns> public static Task<IImageFormat> DetectFormatAsync(
public static IImageInfo Identify(string filePath) string path,
=> Identify(filePath, out IImageFormat _); CancellationToken cancellationToken = default)
=> DetectFormatAsync(DecoderOptions.Default, path, 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="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo 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.
/// </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="path">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> /// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</returns>
/// <returns> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </returns> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format) /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static async Task<IImageFormat> DetectFormatAsync(
DecoderOptions options,
string path,
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(path);
return await DetectFormatAsync(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="path">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</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 path is null.</exception>
/// <returns> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// </returns> public static ImageInfo Identify(string path)
public static Task<IImageInfo> IdentifyAsync(string filePath, CancellationToken cancellationToken = default) => Identify(DecoderOptions.Default, path);
=> IdentifyAsync(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 file path without fully decoding it.
/// </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="path">The image file to open and to read the header from.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>The <see cref="ImageInfo"/>.</returns>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <returns> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found. /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// </returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static async Task<IImageInfo> IdentifyAsync( public static ImageInfo Identify(DecoderOptions options, string path)
DecoderOptions options,
string filePath,
CancellationToken cancellationToken = default)
{ {
(IImageInfo 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(path);
return Identify(options, stream);
} }
/// <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> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="path">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 options are null.</exception>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{ImageInfo}"/> representing the asynchronous operation.
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( /// <exception cref="ArgumentNullException">The path is null.</exception>
string filePath, /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
CancellationToken cancellationToken = default) /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
=> IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken); /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Task<ImageInfo> IdentifyAsync(string path, CancellationToken cancellationToken = default)
=> IdentifyAsync(DecoderOptions.Default, path, 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.
/// </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="path">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>
/// <returns> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{ImageInfo}"/> representing the asynchronous operation.
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static async Task<ImageInfo> IdentifyAsync(
DecoderOptions options, DecoderOptions options,
string filePath, string path,
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(path);
return await IdentifyWithFormatAsync(options, stream, cancellationToken) return await IdentifyAsync(options, stream, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Creates a new instance of the <see cref="Image"/> class from the given file path.
/// The pixel format is automatically determined by the decoder.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <returns><see cref="Image"/>.</returns>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Image Load(string path) public static Image Load(string path)
=> Load(DecoderOptions.Default, path); => Load(DecoderOptions.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Creates a new instance of the <see cref="Image"/> class from the given file path.
/// </summary> /// The pixel format is automatically determined by the decoder.
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image Load(string path, out IImageFormat format)
=> Load(DecoderOptions.Default, path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <returns><see cref="Image"/>.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(DecoderOptions options, string path) public static Image Load(DecoderOptions options, string path)
=> Load(options, path, out _);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the 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 path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync(
DecoderOptions options,
string path,
CancellationToken cancellationToken = default)
{ {
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
using Stream stream = options.Configuration.FileSystem.OpenRead(path); using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) return Load(options, stream);
.ConfigureAwait(false);
return img;
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Creates a new instance of the <see cref="Image"/> class from the given file path.
/// The pixel format is automatically determined by the decoder.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</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 path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default) public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default)
=> LoadAsync(DecoderOptions.Default, path, cancellationToken); => LoadAsync(DecoderOptions.Default, path, cancellationToken);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Creates a new instance of the <see cref="Image"/> class from the given file path.
/// </summary> /// The pixel format is automatically determined by the decoder.
/// <param name="path">The file path to the 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 path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(string path, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(DecoderOptions.Default, path, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</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 path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>( /// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static async Task<Image> LoadAsync(
DecoderOptions options, DecoderOptions options,
string path, string path,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options));
using Stream stream = options.Configuration.FileSystem.OpenRead(path); using Stream stream = options.Configuration.FileSystem.OpenRead(path);
(Image<TPixel> img, _) = return await LoadAsync(options, stream, cancellationToken).ConfigureAwait(false);
await LoadWithFormatAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
return img;
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given file path.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <returns><see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, path); => Load<TPixel>(DecoderOptions.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given file path.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <returns><see cref="Image{TPixel}"/>.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(DecoderOptions options, string path) public static Image<TPixel> Load<TPixel>(DecoderOptions options, string path)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -316,47 +255,43 @@ public abstract partial class Image
} }
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given file path.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="format">The mime 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> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> public static Task<Image<TPixel>> LoadAsync<TPixel>(string path, CancellationToken cancellationToken = default)
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(DecoderOptions options, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => LoadAsync<TPixel>(DecoderOptions.Default, path, cancellationToken);
Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path));
using Stream stream = options.Configuration.FileSystem.OpenRead(path);
return Load<TPixel>(options, stream, out format);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given file path.
/// The pixel type is selected by the decoder.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="format">The mime 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> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">The options are null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="NotSupportedException">The file stream is not readable or the image format is not supported.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> public static async Task<Image<TPixel>> LoadAsync<TPixel>(
public static Image Load(DecoderOptions options, string path, out IImageFormat format) DecoderOptions options,
string path,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
using Stream stream = options.Configuration.FileSystem.OpenRead(path); using Stream stream = options.Configuration.FileSystem.OpenRead(path);
return Load(options, stream, out format); return await LoadAsync<TPixel>(options, stream, cancellationToken).ConfigureAwait(false);
} }
} }

474
src/ImageSharp/Image.FromStream.cs

@ -1,9 +1,6 @@
// 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.Globalization;
using System.Text;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -16,54 +13,62 @@ 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.
/// </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> /// <returns>The <see cref="IImageFormat"/>.</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 or the image format is not supported.</exception>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
public static bool TryDetectFormat(Stream stream, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
=> TryDetectFormat(DecoderOptions.Default, stream, out format); public static IImageFormat DetectFormat(Stream stream)
=> DetectFormat(DecoderOptions.Default, stream);
/// <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.
/// </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> /// <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 or the image format is not supported.</exception>
/// <returns>returns true when format was detected otherwise false.</returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
public static bool TryDetectFormat(DecoderOptions options, Stream stream, [NotNullWhen(true)] out IImageFormat? format) /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
{ public static IImageFormat DetectFormat(DecoderOptions options, Stream stream)
format = WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); => 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. /// Detects the encoded image format type from the specified stream.
/// </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>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</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 or the image format is not supported.</exception>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation or null if none is found.</returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
public static Task<IImageFormat> DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Task<IImageFormat> DetectFormatAsync(
Stream stream,
CancellationToken cancellationToken = default)
=> DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); => DetectFormatAsync(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.
/// </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="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</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 or the image format is not supported.</exception>
/// <returns>A <see cref="Task{IImageFormat}"/> representing the asynchronous operation.</returns> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
public static Task<IImageFormat> DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Task<IImageFormat> DetectFormatAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync( => WithSeekableStreamAsync(
options, options,
stream, stream,
@ -74,136 +79,60 @@ public abstract partial class Image
/// 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> /// </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>
/// <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 or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. public static ImageInfo Identify(Stream stream)
/// </returns> => Identify(DecoderOptions.Default, stream);
public static IImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <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>
/// <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<IImageInfo> IdentifyAsync(Stream stream, CancellationToken cancellationToken = default)
=> IdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
/// <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>
/// <param name="format">The format type of the decoded image.</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="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo 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.
/// </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>
/// <returns>The <see cref="ImageInfo"/>.</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 or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// The <see cref="IImageInfo"/> or null if a suitable info detector is not found. public static ImageInfo Identify(DecoderOptions options, Stream stream)
/// </returns> => WithSeekableStream(options, stream, s => InternalIdentify(options, s));
public static IImageInfo Identify(DecoderOptions options, Stream stream)
=> Identify(options, stream, out _);
/// <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> /// </summary>
/// <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="cancellationToken">The token to monitor for cancellation requests.</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> /// <returns>
/// A <see cref="Task{IImageInfo}"/> representing the asynchronous operation or null if /// The <see cref="Task{ImageInfo}"/> representing the asynchronous operation.
/// a suitable detector is not found.
/// </returns> /// </returns>
public static async Task<IImageInfo> IdentifyAsync( /// <exception cref="ArgumentNullException">The stream is null.</exception>
DecoderOptions options, /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static Task<ImageInfo> IdentifyAsync(
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ => IdentifyAsync(DecoderOptions.Default, stream, cancellationToken);
(IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false);
return res.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.
/// </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="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="IImageInfo"/> or null if a suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format)
{
(IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentify(options, s));
format = data.Format;
return data.ImageInfo;
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// <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> /// <returns>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type /// The <see cref="Task{ImageInfo}"/> representing the asynchronous operation.
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns> /// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
Stream stream,
CancellationToken cancellationToken = default)
=> IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <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 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 or the image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <returns> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// The <see cref="Task{ValueTuple}"/> representing the asynchronous operation with the parameter type public static Task<ImageInfo> IdentifyAsync(
/// <see cref="IImageInfo"/> property set to null if suitable info detector is not found.
/// </returns>
public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
@ -214,301 +143,130 @@ public abstract partial class Image
cancellationToken); cancellationToken);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Creates a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder. /// The pixel format is automatically determined by the decoder.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <returns><see cref="Image"/>.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>The <see cref="Image"/>.</returns> public static Image Load(Stream stream)
public static Image Load(Stream stream, out IImageFormat format) => Load(DecoderOptions.Default, stream);
=> Load(DecoderOptions.Default, stream, out format);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Creates a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder. /// The pixel format is automatically determined by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default)
=> LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <returns><see cref="Image"/>.</returns>
/// <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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>The <see cref="Image"/>.</returns> public static Image Load(DecoderOptions options, Stream stream)
public static Image Load(Stream stream) => Load(DecoderOptions.Default, stream); => WithSeekableStream(options, stream, s => Decode(options, s));
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Creates a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder. /// The pixel format is automatically determined by the decoder.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(Stream stream, CancellationToken cancellationToken = default) public static Task<Image> LoadAsync(Stream stream, CancellationToken cancellationToken = default)
=> LoadAsync(DecoderOptions.Default, stream, cancellationToken); => LoadAsync(DecoderOptions.Default, stream, cancellationToken);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Creates a new instance of the <see cref="Image"/> class from the given stream.
/// </summary> /// The pixel format is automatically determined by the decoder.
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>
public static Image Load(DecoderOptions options, Stream stream)
=> Load(options, stream, out _);
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> public static Task<Image> LoadAsync(
public static async Task<Image> LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) DecoderOptions options,
=> (await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false)).Image; Stream stream,
CancellationToken cancellationToken = default)
=> WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <returns><see cref="Image{TPixel}"/>.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, stream); => Load<TPixel>(DecoderOptions.Default, stream);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadAsync<TPixel>(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(DecoderOptions.Default, stream, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(Stream stream, CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel>
=> LoadWithFormatAsync<TPixel>(DecoderOptions.Default, stream, cancellationToken);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <returns><see cref="Image{TPixel}"/>.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream) public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(options, stream, out IImageFormat _); => WithSeekableStream(options, stream, s => Decode<TPixel>(options, s));
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Image<TPixel> Load<TPixel>(DecoderOptions options, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
(Image<TPixel> Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode<TPixel>(options, s));
format = data.Format;
if (data.Image is null)
{
ThrowNotLoaded(options);
}
return data.Image;
}
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</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 options are null.</exception> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
{
(Image Image, IImageFormat Format) data =
await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync(options, s, ct), cancellationToken)
.ConfigureAwait(false);
if (data.Image is null)
{
ThrowNotLoaded(options);
}
return data;
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</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="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> public static Task<Image<TPixel>> LoadAsync<TPixel>(Stream stream, CancellationToken cancellationToken = default)
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
public static async Task<(Image<TPixel> Image, IImageFormat Format)> LoadWithFormatAsync<TPixel>(
DecoderOptions options,
Stream stream,
CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => LoadAsync<TPixel>(DecoderOptions.Default, stream, cancellationToken);
(Image<TPixel> Image, IImageFormat Format) data =
await WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync<TPixel>(options, s, ct), cancellationToken)
.ConfigureAwait(false);
if (data.Image is null)
{
ThrowNotLoaded(options);
}
return data;
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Creates a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="options">The general decoder options.</param> /// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</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 or the image format is not supported.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="InvalidImageContentException">The encoded image contains invalid content.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> public static Task<Image<TPixel>> LoadAsync<TPixel>(
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task<Image<TPixel>> LoadAsync<TPixel>(
DecoderOptions options, DecoderOptions options,
Stream stream, Stream stream,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => WithSeekableStreamAsync(options, stream, (s, ct) => DecodeAsync<TPixel>(options, s, ct), cancellationToken);
(Image<TPixel> img, _) = await LoadWithFormatAsync<TPixel>(options, stream, cancellationToken)
.ConfigureAwait(false);
return img;
}
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="options">The general decoder options.</param>
/// <param name="stream">The stream containing image information.</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 or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format)
{
(Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s));
format = fmt;
if (img is null)
{
ThrowNotLoaded(options);
}
return img;
}
/// <summary> /// <summary>
/// Performs the given action against the stream ensuring that it is seekable. /// Performs the given action against the stream ensuring that it is seekable.
@ -591,18 +349,4 @@ public abstract partial class Image
return await action(memoryStream, cancellationToken).ConfigureAwait(false); return await action(memoryStream, cancellationToken).ConfigureAwait(false);
} }
[DoesNotReturn]
private static void ThrowNotLoaded(DecoderOptions options)
{
StringBuilder sb = new();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in options.Configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
}
} }

2
src/ImageSharp/Image.LoadPixelData.cs

@ -72,7 +72,7 @@ public abstract partial class Image
int count = width * height; int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(configuration, width, height); Image<TPixel> image = new(configuration, width, height);
data = data[..count]; data = data[..count];
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);

32
src/ImageSharp/Image.cs

@ -14,11 +14,9 @@ namespace SixLabors.ImageSharp;
/// For the non-generic <see cref="Image"/> type, the pixel type is only known at runtime. /// For the non-generic <see cref="Image"/> type, the pixel type is only known at runtime.
/// <see cref="Image"/> is always implemented by a pixel-specific <see cref="Image{TPixel}"/> instance. /// <see cref="Image"/> is always implemented by a pixel-specific <see cref="Image{TPixel}"/> instance.
/// </summary> /// </summary>
public abstract partial class Image : IImage, IConfigurationProvider public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProvider
{ {
private bool isDisposed; private bool isDisposed;
private Size size;
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary> /// <summary>
@ -27,16 +25,12 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// <param name="configuration"> /// <param name="configuration">
/// The configuration which allows altering default behaviour or extending the library. /// The configuration which allows altering default behaviour or extending the library.
/// </param> /// </param>
/// <param name="pixelType">The <see cref="PixelTypeInfo"/>.</param> /// <param name="pixelType">The pixel type information.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param> /// <param name="metadata">The image metadata.</param>
/// <param name="size">The <see cref="size"/>.</param> /// <param name="size">The size in px units.</param>
protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size)
{ : base(pixelType, size, metadata)
this.configuration = configuration ?? Configuration.Default; => this.configuration = configuration ?? Configuration.Default;
this.PixelType = pixelType;
this.size = size;
this.Metadata = metadata ?? new ImageMetadata();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
@ -61,18 +55,6 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// </summary> /// </summary>
protected abstract ImageFrameCollection NonGenericFrameCollection { get; } protected abstract ImageFrameCollection NonGenericFrameCollection { get; }
/// <inheritdoc/>
public PixelTypeInfo PixelType { get; }
/// <inheritdoc />
public int Width => this.size.Width;
/// <inheritdoc />
public int Height => this.size.Height;
/// <inheritdoc/>
public ImageMetadata Metadata { get; }
/// <summary> /// <summary>
/// Gets the frames of the image as (non-generic) <see cref="ImageFrameCollection"/>. /// Gets the frames of the image as (non-generic) <see cref="ImageFrameCollection"/>.
/// </summary> /// </summary>
@ -148,7 +130,7 @@ public abstract partial class Image : IImage, IConfigurationProvider
/// Update the size of the image after mutation. /// Update the size of the image after mutation.
/// </summary> /// </summary>
/// <param name="size">The <see cref="Size"/>.</param> /// <param name="size">The <see cref="Size"/>.</param>
protected void UpdateSize(Size size) => this.size = size; protected void UpdateSize(Size size) => this.Size = size;
/// <summary> /// <summary>
/// Disposes the object and frees resources for the Garbage Collector. /// Disposes the object and frees resources for the Garbage Collector.

4
src/ImageSharp/ImageExtensions.cs

@ -95,7 +95,7 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream."); throw new NotSupportedException("Cannot write to the stream.");
} }
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {
@ -139,7 +139,7 @@ public static partial class ImageExtensions
throw new NotSupportedException("Cannot write to the stream."); throw new NotSupportedException("Cannot write to the stream.");
} }
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format);
if (encoder is null) if (encoder is null)
{ {

58
src/ImageSharp/ImageInfo.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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -9,32 +9,60 @@ namespace SixLabors.ImageSharp;
/// <summary> /// <summary>
/// Contains information about the image including dimensions, pixel type information and additional metadata /// Contains information about the image including dimensions, pixel type information and additional metadata
/// </summary> /// </summary>
internal sealed class ImageInfo : IImageInfo public class ImageInfo
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageInfo"/> class. /// Initializes a new instance of the <see cref="ImageInfo"/> class.
/// </summary> /// </summary>
/// <param name="pixelType">The image pixel type information.</param> /// <param name="pixelType">The pixel type information.</param>
/// <param name="width">The width of the image in pixels.</param> /// <param name="width">The width of the image in px units.</param>
/// <param name="height">The height of the image in pixels.</param> /// <param name="height">The height of the image in px units.</param>
/// <param name="metadata">The images metadata.</param> /// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata) public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata)
: this(pixelType, new(width, height), metadata)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageInfo"/> class.
/// </summary>
/// <param name="pixelType">The pixel type information.</param>
/// <param name="size">The size of the image in px units.</param>
/// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata metadata)
{ {
this.PixelType = pixelType; this.PixelType = pixelType;
this.Width = width; this.Metadata = metadata ?? new ImageMetadata();
this.Height = height; this.Size = size;
this.Metadata = metadata;
} }
/// <inheritdoc /> /// <summary>
/// Gets information about the image pixels.
/// </summary>
public PixelTypeInfo PixelType { get; } public PixelTypeInfo PixelType { get; }
/// <inheritdoc /> /// <summary>
public int Width { get; } /// Gets the image width in px units.
/// </summary>
public int Width => this.Size.Width;
/// <inheritdoc /> /// <summary>
public int Height { get; } /// Gets the image height in px units.
/// </summary>
public int Height => this.Size.Height;
/// <inheritdoc /> /// <summary>
/// Gets any metadata associated wit The image.
/// </summary>
public ImageMetadata Metadata { get; } public ImageMetadata Metadata { get; }
/// <summary>
/// Gets the size of the image in px units.
/// </summary>
public Size Size { get; internal set; }
/// <summary>
/// Gets the bounds of the image.
/// </summary>
public Rectangle Bounds => new(0, 0, this.Width, this.Height);
} }

24
src/ImageSharp/ImageInfoExtensions.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods that allow the addition of geometry calculating methods to the <see cref="IImageInfo"/> type
/// </summary>
public static class ImageInfoExtensions
{
/// <summary>
/// Gets the bounds of the image.
/// </summary>
/// <param name="info">The image info</param>
/// <returns>The <see cref="Size"/></returns>
public static Size Size(this IImageInfo info) => new Size(info.Width, info.Height);
/// <summary>
/// Gets the bounds of the image.
/// </summary>
/// <param name="info">The image info</param>
/// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle Bounds(this IImageInfo info) => new Rectangle(0, 0, info.Width, info.Height);
}

2
src/ImageSharp/Image{TPixel}.cs

@ -411,7 +411,7 @@ public sealed class Image<TPixel> : Image
this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]);
} }
this.UpdateSize(pixelSource.Size()); this.UpdateSize(pixelSource.Size);
} }
private static Size ValidateFramesAndGetSize(IEnumerable<ImageFrame<TPixel>> frames) private static Size ValidateFramesAndGetSize(IEnumerable<ImageFrame<TPixel>> frames)

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

2
src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -95,5 +95,5 @@ internal class DefaultImageProcessorContext<TPixel> : IInternalImageProcessingCo
return this; return this;
} }
private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds(); private Rectangle GetCurrentBounds() => this.destination?.Bounds ?? this.source.Bounds;
} }

2
src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -78,7 +78,7 @@ internal class DrawImageProcessor<TPixelBg, TPixelFg> : ImageProcessor<TPixelBg>
int locationY = this.Location.Y; int locationY = this.Location.Y;
// Align start/end positions. // Align start/end positions.
Rectangle bounds = targetImage.Bounds(); Rectangle bounds = targetImage.Bounds;
int minX = Math.Max(this.Location.X, sourceRectangle.X); int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right);

38
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -27,9 +27,7 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ => this.definition = definition;
this.definition = definition;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
@ -38,7 +36,7 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
// TODO: This is clunky. We should add behavior enum to ExtractFrame. // TODO: This is clunky. We should add behavior enum to ExtractFrame.
// All frames have be the same size so we only need to calculate the correct dimensions for the first frame // All frames have be the same size so we only need to calculate the correct dimensions for the first frame
using (var temp = new Image<TPixel>(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() })) using (Image<TPixel> temp = new(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() }))
{ {
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
@ -52,7 +50,7 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
rectangle = GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); rectangle = GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0);
} }
new CropProcessor(rectangle, this.Source.Size()).Execute(this.Configuration, this.Source, this.SourceRectangle); new CropProcessor(rectangle, this.Source.Size).Execute(this.Configuration, this.Source, this.SourceRectangle);
base.BeforeImageApply(); base.BeforeImageApply();
} }
@ -77,7 +75,7 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) private static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight)
=> new Rectangle( => new(
topLeft.X, topLeft.X,
topLeft.Y, topLeft.Y,
bottomRight.X - topLeft.X, bottomRight.X - topLeft.X,
@ -99,29 +97,13 @@ internal class EntropyCropProcessor<TPixel> : ImageProcessor<TPixel>
int height = bitmap.Height; int height = bitmap.Height;
Point topLeft = default; Point topLeft = default;
Point bottomRight = default; Point bottomRight = default;
Func<ImageFrame<TPixel>, int, int, float, bool> delegateFunc = channel switch
Func<ImageFrame<TPixel>, int, int, float, bool> delegateFunc;
// Determine which channel to check against
switch (channel)
{ {
case RgbaComponent.R: RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon,
delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon,
break; RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon,
_ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon,
case RgbaComponent.G: };
delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon;
break;
case RgbaComponent.B:
delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon;
break;
default:
delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon;
break;
}
int GetMinY(ImageFrame<TPixel> pixels) int GetMinY(ImageFrame<TPixel> pixels)
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)

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

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

2
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -82,7 +82,7 @@ internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>, IResampling
return; return;
} }
var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds);
if (sampler is NearestNeighborResampler) if (sampler is NearestNeighborResampler)
{ {

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

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

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

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

4
tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs

@ -56,8 +56,8 @@ public class DecodeFilteredPng
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Size LoadPng(byte[] bytes) private static Size LoadPng(byte[] bytes)
{ {
using var image = Image.Load<Rgba32>(bytes); using Image<Rgba32> image = Image.Load<Rgba32>(bytes);
return image.Size(); return image.Size;
} }
private static string TestImageFullPath(string path) private static string TestImageFullPath(string path)

17
tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs

@ -22,26 +22,21 @@ public class DecodePng
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
{ => this.pngBytes ??= File.ReadAllBytes(this.TestImageFullPath);
if (this.pngBytes == null)
{
this.pngBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Png")] [Benchmark(Baseline = true, Description = "System.Drawing Png")]
public SDSize PngSystemDrawing() public SDSize PngSystemDrawing()
{ {
using var memoryStream = new MemoryStream(this.pngBytes); using MemoryStream memoryStream = new(this.pngBytes);
using var image = SDImage.FromStream(memoryStream); using SDImage image = SDImage.FromStream(memoryStream);
return image.Size; return image.Size;
} }
[Benchmark(Description = "ImageSharp Png")] [Benchmark(Description = "ImageSharp Png")]
public Size PngImageSharp() public Size PngImageSharp()
{ {
using var memoryStream = new MemoryStream(this.pngBytes); using MemoryStream memoryStream = new(this.pngBytes);
using var image = Image.Load<Rgba32>(memoryStream); using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
return image.Size(); return image.Size;
} }
} }

16
tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs

@ -69,20 +69,16 @@ public class DecodeTiff
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")] [Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public SDSize TiffSystemDrawing() public SDSize TiffSystemDrawing()
{ {
using (var memoryStream = new MemoryStream(this.data)) using MemoryStream memoryStream = new(this.data);
using (var image = SDImage.FromStream(memoryStream)) using SDImage image = SDImage.FromStream(memoryStream);
{ return image.Size;
return image.Size;
}
} }
[Benchmark(Description = "ImageSharp Tiff")] [Benchmark(Description = "ImageSharp Tiff")]
public Size TiffCore() public Size TiffCore()
{ {
using (var ms = new MemoryStream(this.data)) using MemoryStream ms = new(this.data);
using (var image = Image.Load<Rgba32>(ms)) using Image<Rgba32> image = Image.Load<Rgba32>(ms);
{ return image.Size;
return image.Size();
}
} }
} }

8
tests/ImageSharp.Benchmarks/Processing/Diffuse.cs

@ -13,19 +13,19 @@ public class Diffuse
[Benchmark] [Benchmark]
public Size DoDiffuse() public Size DoDiffuse()
{ {
using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond); using Image<Rgba32> image = new(Configuration.Default, 800, 800, Color.BlanchedAlmond);
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
return image.Size(); return image.Size;
} }
[Benchmark] [Benchmark]
public Size DoDither() public Size DoDither()
{ {
using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond); using Image<Rgba32> image = new(Configuration.Default, 800, 800, Color.BlanchedAlmond);
image.Mutate(x => x.Dither()); image.Mutate(x => x.Dither());
return image.Size(); return image.Size;
} }
} }

4
tests/ImageSharp.Benchmarks/Processing/Rotate.cs

@ -13,10 +13,10 @@ public class Rotate
[Benchmark] [Benchmark]
public Size DoRotate() public Size DoRotate()
{ {
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond); using Image<Rgba32> image = new(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Rotate(37.5F)); image.Mutate(x => x.Rotate(37.5F));
return image.Size(); return image.Size;
} }
} }

4
tests/ImageSharp.Benchmarks/Processing/Skew.cs

@ -13,10 +13,10 @@ public class Skew
[Benchmark] [Benchmark]
public Size DoSkew() public Size DoSkew()
{ {
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond); using Image<Rgba32> image = new(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Skew(20, 10)); image.Mutate(x => x.Skew(20, 10));
return image.Size(); return image.Size;
} }
} }

12
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -30,7 +30,7 @@ public class AdvancedImageExtensionsTests
IMemoryGroup<TPixel> memoryGroup = image.GetPixelMemoryGroup(); IMemoryGroup<TPixel> memoryGroup = image.GetPixelMemoryGroup();
// Assert: // Assert:
VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size);
} }
[Theory] [Theory]
@ -57,23 +57,23 @@ public class AdvancedImageExtensionsTests
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using Image<TPixel> image0 = provider.GetImage(); using Image<TPixel> image0 = provider.GetImage();
var targetBuffer = new TPixel[image0.Width * image0.Height]; TPixel[] targetBuffer = new TPixel[image0.Width * image0.Height];
Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory<TPixel> sourceBuffer)); Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory<TPixel> sourceBuffer));
sourceBuffer.CopyTo(targetBuffer); sourceBuffer.CopyTo(targetBuffer);
var managerOfExternalMemory = new TestMemoryManager<TPixel>(targetBuffer); TestMemoryManager<TPixel> managerOfExternalMemory = new(targetBuffer);
Memory<TPixel> externalMemory = managerOfExternalMemory.Memory; Memory<TPixel> externalMemory = managerOfExternalMemory.Memory;
using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) using (Image<TPixel> image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height))
{ {
VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size);
} }
// Make sure externalMemory works after destruction: // Make sure externalMemory works after destruction:
VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size);
} }
private static void VerifyMemoryGroupDataMatchesTestPattern<TPixel>( private static void VerifyMemoryGroupDataMatchesTestPattern<TPixel>(

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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
{ ImageInfo imageInfo = Image.Identify(stream);
IImageInfo 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);
}
} }
} }

72
tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs

@ -15,15 +15,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsBmp_Path.bmp"); string file = Path.Combine(dir, "SaveAsBmp_Path.bmp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsBmp(file); image.SaveAsBmp(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -32,15 +30,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp"); string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsBmpAsync(file); await image.SaveAsBmpAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -49,15 +45,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp"); string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsBmp(file, new BmpEncoder()); image.SaveAsBmp(file, new BmpEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -66,86 +60,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp"); string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsBmpAsync(file, new BmpEncoder()); await image.SaveAsBmpAsync(file, new BmpEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsBmp_Stream() public void SaveAsBmp_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsBmp(memoryStream); image.SaveAsBmp(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsBmpAsync_StreamAsync() public async Task SaveAsBmpAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsBmpAsync(memoryStream); await image.SaveAsBmpAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsBmp_Stream_Encoder() public void SaveAsBmp_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsBmp(memoryStream, new BmpEncoder()); image.SaveAsBmp(memoryStream, new BmpEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsBmpAsync_Stream_Encoder() public async Task SaveAsBmpAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsBmpAsync(memoryStream, new BmpEncoder()); await image.SaveAsBmpAsync(memoryStream, new BmpEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is BmpFormat);
Assert.Equal("image/bmp", mime.DefaultMimeType);
}
} }
} }

19
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;
@ -208,10 +207,10 @@ public class GeneralFormatTests
foreach (TestFile file in Files) foreach (TestFile file in Files)
{ {
byte[] serialized; byte[] serialized;
using (Image image = Image.Load(file.Bytes, out IImageFormat mimeType)) using (Image image = Image.Load(file.Bytes))
using (MemoryStream memoryStream = new()) using (MemoryStream memoryStream = new())
{ {
image.Save(memoryStream, mimeType); image.Save(memoryStream, image.Metadata.DecodedImageFormat);
memoryStream.Flush(); memoryStream.Flush();
serialized = memoryStream.ToArray(); serialized = memoryStream.ToArray();
} }
@ -257,27 +256,21 @@ public class GeneralFormatTests
image.Save(memoryStream, format); image.Save(memoryStream, format);
memoryStream.Position = 0; memoryStream.Position = 0;
IImageInfo imageInfo = Image.Identify(memoryStream); ImageInfo imageInfo = Image.Identify(memoryStream);
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]
public void IdentifyReturnsNullWithInvalidStream() public void Identify_UnknownImageFormatException_WithInvalidStream()
{ {
byte[] invalid = new byte[10]; byte[] invalid = new byte[10];
using MemoryStream memoryStream = new(invalid); using MemoryStream memoryStream = new(invalid);
IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format);
Assert.Null(imageInfo); Assert.Throws<UnknownImageFormatException>(() => Image.Identify(invalid));
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);
ImageInfo imageInfo = Image.Identify(stream);
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++)
{ {

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

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

72
tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs

@ -15,15 +15,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsGif_Path.gif"); string file = Path.Combine(dir, "SaveAsGif_Path.gif");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsGif(file); image.SaveAsGif(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -32,15 +30,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif"); string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsGifAsync(file); await image.SaveAsGifAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -49,15 +45,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif"); string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsGif(file, new GifEncoder()); image.SaveAsGif(file, new GifEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -66,86 +60,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif"); string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsGifAsync(file, new GifEncoder()); await image.SaveAsGifAsync(file, new GifEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsGif_Stream() public void SaveAsGif_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsGif(memoryStream); image.SaveAsGif(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsGifAsync_StreamAsync() public async Task SaveAsGifAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsGifAsync(memoryStream); await image.SaveAsGifAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsGif_Stream_Encoder() public void SaveAsGif_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsGif(memoryStream, new GifEncoder()); image.SaveAsGif(memoryStream, new GifEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsGifAsync_Stream_Encoder() public async Task SaveAsGifAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsGifAsync(memoryStream, new GifEncoder()); await image.SaveAsGifAsync(memoryStream, new GifEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is GifFormat);
Assert.Equal("image/gif", mime.DefaultMimeType);
}
} }
} }

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

@ -74,12 +74,12 @@ public class ImageFormatManagerTests
{ {
IImageEncoder encoder1 = new Mock<IImageEncoder>().Object; IImageEncoder encoder1 = new Mock<IImageEncoder>().Object;
this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1);
IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); IImageEncoder found = this.FormatsManagerEmpty.GetEncoder(TestFormat.GlobalTestFormat);
Assert.Equal(encoder1, found); Assert.Equal(encoder1, found);
IImageEncoder encoder2 = new Mock<IImageEncoder>().Object; IImageEncoder encoder2 = new Mock<IImageEncoder>().Object;
this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2);
IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); IImageEncoder found2 = this.FormatsManagerEmpty.GetEncoder(TestFormat.GlobalTestFormat);
Assert.Equal(encoder2, found2); Assert.Equal(encoder2, found2);
Assert.NotEqual(found, found2); Assert.NotEqual(found, found2);
} }
@ -89,12 +89,12 @@ public class ImageFormatManagerTests
{ {
IImageDecoder decoder1 = new Mock<IImageDecoder>().Object; IImageDecoder decoder1 = new Mock<IImageDecoder>().Object;
this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1);
IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); IImageDecoder found = this.FormatsManagerEmpty.GetDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder1, found); Assert.Equal(decoder1, found);
IImageDecoder decoder2 = new Mock<IImageDecoder>().Object; IImageDecoder decoder2 = new Mock<IImageDecoder>().Object;
this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2);
IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); IImageDecoder found2 = this.FormatsManagerEmpty.GetDecoder(TestFormat.GlobalTestFormat);
Assert.Equal(decoder2, found2); Assert.Equal(decoder2, found2);
Assert.NotEqual(found, found2); Assert.NotEqual(found, found2);
} }
@ -120,15 +120,10 @@ public class ImageFormatManagerTests
jpegImage = buffer.ToArray(); jpegImage = buffer.ToArray();
} }
byte[] invalidImage = { 1, 2, 3 }; IImageFormat format = Image.DetectFormat(jpegImage);
Assert.IsType<JpegFormat>(format);
bool resultValidImage = Image.TryDetectFormat(jpegImage, out IImageFormat format);
bool resultInvalidImage = Image.TryDetectFormat(invalidImage, out IImageFormat formatInvalid); byte[] invalidImage = { 1, 2, 3 };
Assert.Throws<UnknownImageFormatException>(() => Image.DetectFormat(invalidImage));
Assert.True(resultValidImage);
Assert.Equal(format, JpegFormat.Instance);
Assert.False(resultInvalidImage);
Assert.True(formatInvalid is null);
} }
} }

72
tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs

@ -16,15 +16,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg"); string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsJpeg(file); image.SaveAsJpeg(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -33,15 +31,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg"); string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsJpegAsync(file); await image.SaveAsJpegAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -50,15 +46,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg"); string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsJpeg(file, new JpegEncoder()); image.SaveAsJpeg(file, new JpegEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -67,86 +61,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg"); string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsJpegAsync(file, new JpegEncoder()); await image.SaveAsJpegAsync(file, new JpegEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsJpeg_Stream() public void SaveAsJpeg_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsJpeg(memoryStream); image.SaveAsJpeg(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsJpegAsync_StreamAsync() public async Task SaveAsJpegAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsJpegAsync(memoryStream); await image.SaveAsJpegAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsJpeg_Stream_Encoder() public void SaveAsJpeg_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsJpeg(memoryStream, new JpegEncoder()); image.SaveAsJpeg(memoryStream, new JpegEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsJpegAsync_Stream_Encoder() public async Task SaveAsJpegAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsJpegAsync(memoryStream, new JpegEncoder()); await image.SaveAsJpegAsync(memoryStream, new JpegEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is JpegFormat);
Assert.Equal("image/jpeg", mime.DefaultMimeType);
}
} }
} }

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

@ -75,8 +75,8 @@ public partial class JpegDecoderTests
[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 image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream); using Image image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
@ -88,9 +88,9 @@ public partial class JpegDecoderTests
[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);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
@ -101,9 +101,9 @@ public partial class JpegDecoderTests
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public async Task Identify_VerifyRatioAsync(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);
IImageInfo image = await JpegDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream); ImageInfo image = await JpegDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata; ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(xResolution, meta.HorizontalResolution);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
@ -114,9 +114,9 @@ public partial class JpegDecoderTests
[MemberData(nameof(QualityFiles))] [MemberData(nameof(QualityFiles))]
public void Identify_VerifyQuality(string imagePath, int quality) public void Identify_VerifyQuality(string imagePath, int quality)
{ {
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);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality); Assert.Equal(quality, meta.Quality);
} }
@ -125,8 +125,8 @@ public partial class JpegDecoderTests
[MemberData(nameof(QualityFiles))] [MemberData(nameof(QualityFiles))]
public void Decode_VerifyQuality(string imagePath, int quality) public void Decode_VerifyQuality(string imagePath, int quality)
{ {
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 image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream); using Image image = JpegDecoder.Instance.Decode(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality); Assert.Equal(quality, meta.Quality);
@ -136,8 +136,8 @@ public partial class JpegDecoderTests
[MemberData(nameof(QualityFiles))] [MemberData(nameof(QualityFiles))]
public async Task Decode_VerifyQualityAsync(string imagePath, int quality) public async Task Decode_VerifyQualityAsync(string imagePath, int quality)
{ {
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 image = await JpegDecoder.Instance.DecodeAsync(DecoderOptions.Default, stream); using Image image = await JpegDecoder.Instance.DecodeAsync(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality); Assert.Equal(quality, meta.Quality);
@ -153,9 +153,9 @@ public partial class JpegDecoderTests
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)]
public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor 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);
IImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream); ImageInfo image = JpegDecoder.Instance.Identify(DecoderOptions.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType); Assert.Equal(expectedColorType, meta.ColorType);
} }
@ -174,13 +174,13 @@ public partial class JpegDecoderTests
Assert.Equal(expectedColorType, meta.ColorType); Assert.Equal(expectedColorType, meta.ColorType);
} }
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test) private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<ImageInfo> test)
{ {
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);
if (useIdentify) if (useIdentify)
{ {
IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream); ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo); test(imageInfo);
} }
else else
@ -318,10 +318,10 @@ public partial class JpegDecoderTests
[Fact] [Fact]
public void EncodedStringTags_WriteAndRead() public void EncodedStringTags_WriteAndRead()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora))) using (Image image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora)))
{ {
var exif = new ExifProfile(); ExifProfile exif = new();
exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06"); exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06");
@ -343,7 +343,7 @@ public partial class JpegDecoderTests
} }
memoryStream.Seek(0, SeekOrigin.Begin); memoryStream.Seek(0, SeekOrigin.Begin);
using (var image = Image.Load(memoryStream)) using (Image image = Image.Load(memoryStream))
{ {
ExifProfile exif = image.Metadata.ExifProfile; ExifProfile exif = image.Metadata.ExifProfile;
VerifyEncodedStrings(exif); VerifyEncodedStrings(exif);
@ -353,7 +353,7 @@ public partial class JpegDecoderTests
[Fact] [Fact]
public void EncodedStringTags_Read() public void EncodedStringTags_Read()
{ {
using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)); using Image image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings));
ExifProfile exif = image.Metadata.ExifProfile; ExifProfile exif = image.Metadata.ExifProfile;
VerifyEncodedStrings(exif); VerifyEncodedStrings(exif);
} }

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

@ -35,7 +35,7 @@ public partial class JpegDecoderTests
if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) if (!CustomToleranceValues.TryGetValue(file, out float tolerance))
{ {
bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0; bool baseline = file.Contains("baseline", StringComparison.OrdinalIgnoreCase);
tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; tolerance = baseline ? BaselineTolerance : ProgressiveTolerance;
} }
@ -68,9 +68,9 @@ public partial class JpegDecoderTests
{ {
JpegDecoderOptions options = new(); JpegDecoderOptions options = new();
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using var ms = new MemoryStream(bytes); using MemoryStream ms = new(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using BufferedReadStream bufferedStream = new(Configuration.Default, ms);
using var decoder = new JpegDecoderCore(options); using JpegDecoderCore decoder = new(options);
using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default); using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default);
// I don't know why these numbers are different. All I know is that the decoder works // I don't know why these numbers are different. All I know is that the decoder works
@ -85,7 +85,7 @@ public partial class JpegDecoderTests
public void Decode_NonGeneric_CreatesRgb24Image() public void Decode_NonGeneric_CreatesRgb24Image()
{ {
string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small);
using var image = Image.Load(file); using Image image = Image.Load(file);
Assert.IsType<Image<Rgb24>>(image); Assert.IsType<Image<Rgb24>>(image);
} }

72
tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs

@ -15,15 +15,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); string file = Path.Combine(dir, "SaveAsPbm_Path.pbm");
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
image.SaveAsPbm(file); image.SaveAsPbm(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -32,15 +30,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm");
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
await image.SaveAsPbmAsync(file); await image.SaveAsPbmAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -49,15 +45,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm");
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
image.SaveAsPbm(file, new PbmEncoder()); image.SaveAsPbm(file, new PbmEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -66,86 +60,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm");
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
await image.SaveAsPbmAsync(file, new PbmEncoder()); await image.SaveAsPbmAsync(file, new PbmEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsPbm_Stream() public void SaveAsPbm_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
image.SaveAsPbm(memoryStream); image.SaveAsPbm(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsPbmAsync_StreamAsync() public async Task SaveAsPbmAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
await image.SaveAsPbmAsync(memoryStream); await image.SaveAsPbmAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsPbm_Stream_Encoder() public void SaveAsPbm_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
image.SaveAsPbm(memoryStream, new PbmEncoder()); image.SaveAsPbm(memoryStream, new PbmEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsPbmAsync_Stream_Encoder() public async Task SaveAsPbmAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<L8>(10, 10)) using (Image<L8> image = new(10, 10))
{ {
await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); await image.SaveAsPbmAsync(memoryStream, new PbmEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PbmFormat);
Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType);
}
} }
} }

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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata();
Assert.NotNull(bitmapMetadata); Assert.NotNull(bitmapMetadata);

72
tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs

@ -16,15 +16,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsPng_Path.png"); string file = Path.Combine(dir, "SaveAsPng_Path.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsPng(file); image.SaveAsPng(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -33,15 +31,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsPngAsync_Path.png"); string file = Path.Combine(dir, "SaveAsPngAsync_Path.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsPngAsync(file); await image.SaveAsPngAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -50,15 +46,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png"); string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsPng(file, new PngEncoder()); image.SaveAsPng(file, new PngEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -67,86 +61,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png"); string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsPngAsync(file, new PngEncoder()); await image.SaveAsPngAsync(file, new PngEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsPng_Stream() public void SaveAsPng_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsPng(memoryStream); image.SaveAsPng(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsPngAsync_StreamAsync() public async Task SaveAsPngAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsPngAsync(memoryStream); await image.SaveAsPngAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsPng_Stream_Encoder() public void SaveAsPng_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsPng(memoryStream, new PngEncoder()); image.SaveAsPng(memoryStream, new PngEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsPngAsync_Stream_Encoder() public async Task SaveAsPngAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsPngAsync(memoryStream, new PngEncoder()); await image.SaveAsPngAsync(memoryStream, new PngEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
} }

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);
ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); PngMetadata metadata = imageInfo.Metadata.GetPngMetadata();
Assert.True(metadata.HasTransparency); Assert.True(metadata.HasTransparency);
} }

48
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,9 +217,9 @@ 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);
IImageInfo 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);
Assert.Equal(yResolution, meta.VerticalResolution); Assert.Equal(yResolution, meta.VerticalResolution);
@ -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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo); Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.Metadata.ExifProfile); Assert.NotNull(imageInfo.Metadata.ExifProfile);

72
tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs

@ -15,15 +15,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTga_Path.tga"); string file = Path.Combine(dir, "SaveAsTga_Path.tga");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTga(file); image.SaveAsTga(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -32,15 +30,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga"); string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTgaAsync(file); await image.SaveAsTgaAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -49,15 +45,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga"); string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTga(file, new TgaEncoder()); image.SaveAsTga(file, new TgaEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -66,86 +60,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga"); string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTgaAsync(file, new TgaEncoder()); await image.SaveAsTgaAsync(file, new TgaEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsTga_Stream() public void SaveAsTga_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTga(memoryStream); image.SaveAsTga(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsTgaAsync_StreamAsync() public async Task SaveAsTgaAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTgaAsync(memoryStream); await image.SaveAsTgaAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsTga_Stream_Encoder() public void SaveAsTga_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTga(memoryStream, new TgaEncoder()); image.SaveAsTga(memoryStream, new TgaEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsTgaAsync_Stream_Encoder() public async Task SaveAsTgaAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTgaAsync(memoryStream, new TgaEncoder()); await image.SaveAsTgaAsync(memoryStream, new TgaEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TgaFormat);
Assert.Equal("image/tga", mime.DefaultMimeType);
}
} }
} }

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

@ -22,11 +22,11 @@ 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>(() =>
{ {
using (Image.Load(DecoderOptions.Default, stream, out IImageFormat _)) using (Image.Load(DecoderOptions.Default, stream))
{ {
} }
}); });
@ -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);
IImageInfo info = Image.Identify(stream); ImageInfo info = Image.Identify(stream);
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);
{ ImageInfo info = Image.Identify(stream);
IImageInfo 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);
{ ImageInfo info = Image.Identify(stream);
IImageInfo 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]

72
tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs

@ -16,15 +16,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); string file = Path.Combine(dir, "SaveAsTiff_Path.tiff");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTiff(file); image.SaveAsTiff(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -33,15 +31,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTiffAsync(file); await image.SaveAsTiffAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -50,15 +46,13 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTiff(file, new TiffEncoder()); image.SaveAsTiff(file, new TiffEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -67,86 +61,76 @@ public class ImageExtensionsTest
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTiffAsync(file, new TiffEncoder()); await image.SaveAsTiffAsync(file, new TiffEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsTiff_Stream() public void SaveAsTiff_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTiff(memoryStream); image.SaveAsTiff(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsTiffAsync_StreamAsync() public async Task SaveAsTiffAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTiffAsync(memoryStream); await image.SaveAsTiffAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsTiff_Stream_Encoder() public void SaveAsTiff_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsTiff(memoryStream, new TiffEncoder()); image.SaveAsTiff(memoryStream, new TiffEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsTiffAsync_Stream_Encoder() public async Task SaveAsTiffAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); await image.SaveAsTiffAsync(memoryStream, new TiffEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is TiffFormat);
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
} }
} }

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);
{ ImageInfo info = Image.Identify(stream);
IImageInfo 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);
{ ImageInfo info = Image.Identify(stream);
IImageInfo 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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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;

72
tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs

@ -16,15 +16,13 @@ public class ImageExtensionsTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests));
string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); string file = Path.Combine(dir, "SaveAsWebp_Path.webp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsWebp(file); image.SaveAsWebp(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -33,15 +31,13 @@ public class ImageExtensionsTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests));
string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsWebpAsync(file); await image.SaveAsWebpAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -50,15 +46,13 @@ public class ImageExtensionsTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsWebp(file, new WebpEncoder()); image.SaveAsWebp(file, new WebpEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
@ -67,86 +61,76 @@ public class ImageExtensionsTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsWebpAsync(file, new WebpEncoder()); await image.SaveAsWebpAsync(file, new WebpEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsWebp_Stream() public void SaveAsWebp_Stream()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsWebp(memoryStream); image.SaveAsWebp(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsWebpAsync_StreamAsync() public async Task SaveAsWebpAsync_StreamAsync()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsWebpAsync(memoryStream); await image.SaveAsWebpAsync(memoryStream);
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void SaveAsWebp_Stream_Encoder() public void SaveAsWebp_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.SaveAsWebp(memoryStream, new WebpEncoder()); image.SaveAsWebp(memoryStream, new WebpEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task SaveAsWebpAsync_Stream_Encoder() public async Task SaveAsWebpAsync_Stream_Encoder()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); await image.SaveAsWebpAsync(memoryStream, new WebpEncoder());
} }
memoryStream.Position = 0; memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(memoryStream);
{ Assert.True(format is WebpFormat);
Assert.Equal("image/webp", mime.DefaultMimeType);
}
} }
} }

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);
IImageInfo imageInfo = Image.Identify(stream); ImageInfo imageInfo = Image.Identify(stream);
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);

12
tests/ImageSharp.Tests/Image/ImageRotationTests.cs

@ -45,12 +45,10 @@ public class ImageRotationTests
private static (Size Original, Size Rotated) Rotate(int angle) private static (Size Original, Size Rotated) Rotate(int angle)
{ {
var file = TestFile.Create(TestImages.Bmp.Car); TestFile file = TestFile.Create(TestImages.Bmp.Car);
using (var image = Image.Load<Rgba32>(file.FullPath)) using Image<Rgba32> image = Image.Load<Rgba32>(file.FullPath);
{ Size original = image.Size;
Size original = image.Size(); image.Mutate(x => x.Rotate(angle));
image.Mutate(x => x.Rotate(angle)); return (original, image.Size);
return (original, image.Size());
}
} }
} }

82
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,10 +34,8 @@ public partial class ImageTests
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
{ {
bool result = Image.TryDetectFormat(this.ActualImageSpan, out IImageFormat type); IImageFormat format = Image.DetectFormat(ActualImageSpan);
Assert.Equal(ExpectedGlobalFormat, format);
Assert.True(result);
Assert.Equal(ExpectedGlobalFormat, type);
} }
[Fact] [Fact]
@ -48,19 +46,22 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
bool result = Image.TryDetectFormat(options, this.ByteArray, out IImageFormat type); IImageFormat format = Image.DetectFormat(options, this.ByteArray);
Assert.Equal(this.LocalImageFormat, format);
Assert.True(result);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void FromFileSystemPath_GlobalConfiguration() public void FromFileSystemPath_GlobalConfiguration()
{ {
bool result = Image.TryDetectFormat(ActualImagePath, out IImageFormat type); IImageFormat format = Image.DetectFormat(ActualImagePath);
Assert.Equal(ExpectedGlobalFormat, format);
}
Assert.True(result); [Fact]
Assert.Equal(ExpectedGlobalFormat, type); public async Task FromFileSystemPathAsync_GlobalConfiguration()
{
IImageFormat format = await Image.DetectFormatAsync(ActualImagePath);
Assert.Equal(ExpectedGlobalFormat, format);
} }
[Fact] [Fact]
@ -71,22 +72,29 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
bool result = Image.TryDetectFormat(options, this.MockFilePath, out IImageFormat type); IImageFormat format = Image.DetectFormat(options, this.MockFilePath);
Assert.Equal(this.LocalImageFormat, format);
}
Assert.True(result); [Fact]
Assert.Equal(this.LocalImageFormat, type); public async Task FromFileSystemPathAsync_CustomConfiguration()
{
DecoderOptions options = new()
{
Configuration = this.LocalConfiguration
};
IImageFormat format = await Image.DetectFormatAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageFormat, format);
} }
[Fact] [Fact]
public void FromStream_GlobalConfiguration() public void FromStream_GlobalConfiguration()
{ {
using (var stream = new MemoryStream(this.ActualImageBytes)) using MemoryStream stream = new(ActualImageBytes);
{ IImageFormat format = Image.DetectFormat(stream);
bool result = Image.TryDetectFormat(stream, out IImageFormat type);
Assert.True(result); Assert.Equal(ExpectedGlobalFormat, format);
Assert.Equal(ExpectedGlobalFormat, type);
}
} }
[Fact] [Fact]
@ -97,34 +105,27 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
bool result = Image.TryDetectFormat(options, this.DataStream, out IImageFormat type); IImageFormat format = Image.DetectFormat(options, this.DataStream);
Assert.Equal(this.LocalImageFormat, format);
Assert.True(result);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
public void WhenNoMatchingFormatFound_ReturnsNull() public void WhenNoMatchingFormatFound_Throws_UnknownImageFormatException()
{ {
DecoderOptions options = new() DecoderOptions options = new()
{ {
Configuration = new() Configuration = new()
}; };
bool result = Image.TryDetectFormat(options, this.DataStream, out IImageFormat type); Assert.Throws<UnknownImageFormatException>(() => Image.DetectFormat(options, this.DataStream));
Assert.False(result);
Assert.Null(type);
} }
[Fact] [Fact]
public async Task FromStreamAsync_GlobalConfiguration() public async Task FromStreamAsync_GlobalConfiguration()
{ {
using (var stream = new MemoryStream(this.ActualImageBytes)) using MemoryStream stream = new(ActualImageBytes);
{ IImageFormat format = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false));
IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); Assert.Equal(ExpectedGlobalFormat, format);
Assert.Equal(ExpectedGlobalFormat, type);
}
} }
[Fact] [Fact]
@ -135,20 +136,19 @@ public partial class ImageTests
Configuration = this.LocalConfiguration Configuration = this.LocalConfiguration
}; };
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); IImageFormat format = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false));
Assert.Equal(this.LocalImageFormat, type); Assert.Equal(this.LocalImageFormat, format);
} }
[Fact] [Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() public Task WhenNoMatchingFormatFoundAsync_Throws_UnknownImageFormatException()
{ {
DecoderOptions options = new() DecoderOptions options = new()
{ {
Configuration = new() Configuration = new()
}; };
IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); return Assert.ThrowsAsync<UnknownImageFormatException>(async () => await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)));
Assert.Null(type);
} }
} }
} }

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

@ -21,47 +21,39 @@ public partial class ImageTests
private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes;
private IImageInfo LocalImageInfo => this.localImageInfoMock.Object;
private IImageFormat LocalImageFormat => this.localImageFormatMock.Object;
private static IImageFormat ExpectedGlobalFormat private static IImageFormat ExpectedGlobalFormat
{ {
get get
{ {
Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out var format); Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("bmp", out IImageFormat format);
return format!; return format!;
} }
} }
[Fact] [Fact]
public void FromBytes_GlobalConfiguration() public void FromBytes_GlobalConfiguration()
{ {
IImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type); ImageInfo info = Image.Identify(ActualImageBytes);
Assert.Equal(ExpectedImageSize, info.Size);
Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
Assert.Equal(ExpectedGlobalFormat, type);
} }
[Fact] [Fact]
public void FromBytes_CustomConfiguration() public void FromBytes_CustomConfiguration()
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
ImageInfo info = Image.Identify(options, this.ByteArray);
IImageInfo 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()
{ {
IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); ImageInfo info = Image.Identify(ActualImagePath);
Assert.NotNull(info); Assert.NotNull(info);
Assert.Equal(ExpectedGlobalFormat, type); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
@ -69,27 +61,26 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); ImageInfo info = Image.Identify(options, this.MockFilePath);
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);
IImageInfo info = Image.Identify(stream, out IImageFormat type); ImageInfo info = Image.Identify(stream);
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);
IImageInfo info = Image.Identify(stream); ImageInfo info = Image.Identify(stream);
Assert.NotNull(info); Assert.NotNull(info);
} }
@ -97,22 +88,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);
IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type); ImageInfo info = Image.Identify(nonSeekableStream);
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);
IImageInfo info = Image.Identify(nonSeekableStream); ImageInfo info = Image.Identify(nonSeekableStream);
Assert.NotNull(info); Assert.NotNull(info);
} }
@ -122,10 +113,9 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); ImageInfo info = Image.Identify(options, this.DataStream);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, type);
} }
[Fact] [Fact]
@ -133,27 +123,24 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = Image.Identify(options, this.DataStream); ImageInfo info = Image.Identify(options, this.DataStream);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
} }
[Fact] [Fact]
public void WhenNoMatchingFormatFound_ReturnsNull() public void WhenNoMatchingFormatFound_Throws_UnknownImageFormatException()
{ {
DecoderOptions options = new() { Configuration = new() }; DecoderOptions options = new() { Configuration = new() };
IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); Assert.Throws<UnknownImageFormatException>(() => Image.Identify(options, this.DataStream));
Assert.Null(info);
Assert.Null(type);
} }
[Fact] [Fact]
public void FromStream_ZeroLength_ReturnsNull() public void FromStream_ZeroLength_Throws_UnknownImageFormatException()
{ {
// 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,
@ -168,39 +155,39 @@ 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();
IImageInfo info = Image.Identify(stream);
Assert.Null(info); Assert.Throws<UnknownImageFormatException>(() => Image.Identify(stream));
} }
[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);
IImageInfo info = await Image.IdentifyAsync(asyncStream);
ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info); Assert.NotNull(info);
} }
[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);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.Equal(ExpectedImageSize, info.Size);
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedGlobalFormat, info.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);
IImageInfo info = await Image.IdentifyAsync(asyncStream); ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.NotNull(info); Assert.NotNull(info);
} }
@ -208,21 +195,21 @@ public partial class ImageTests
[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);
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); ImageInfo info = await Image.IdentifyAsync(asyncStream);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.Equal(ExpectedImageSize, info.Size);
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task FromStreamAsync_ZeroLength_ReturnsNull() public async Task FromStreamAsync_ZeroLength_Throws_UnknownImageFormatException()
{ {
// 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,
@ -237,8 +224,8 @@ 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();
IImageInfo info = await Image.IdentifyAsync(stream);
Assert.Null(info); await Assert.ThrowsAsync<UnknownImageFormatException>(async () => await Image.IdentifyAsync(stream));
} }
[Fact] [Fact]
@ -246,7 +233,8 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageInfo, info);
} }
@ -255,27 +243,26 @@ public partial class ImageTests
{ {
DecoderOptions options = new() { Configuration = this.LocalConfiguration }; DecoderOptions options = new() { Configuration = this.LocalConfiguration };
(IImageInfo ImageInfo, IImageFormat Format) info = ImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath);
await Image.IdentifyWithFormatAsync(options, this.MockFilePath);
Assert.NotNull(info.ImageInfo); Assert.NotNull(info);
Assert.Equal(this.LocalImageFormat, info.Format);
} }
[Fact] [Fact]
public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration()
{ {
(IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); ImageInfo info = await Image.IdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); Assert.Equal(ExpectedImageSize, info.Size);
Assert.Equal(ExpectedGlobalFormat, res.Format); Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task FromPathAsync_GlobalConfiguration() public async Task FromPathAsync_GlobalConfiguration()
{ {
IImageInfo info = await Image.IdentifyAsync(ActualImagePath); ImageInfo info = await Image.IdentifyAsync(ActualImagePath);
Assert.Equal(ExpectedImageSize, info.Size()); Assert.Equal(ExpectedImageSize, info.Size);
} }
[Fact] [Fact]
@ -283,24 +270,19 @@ 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);
(IImageInfo ImageInfo, IImageFormat Format) ImageInfo info = await Image.IdentifyAsync(options, asyncStream);
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.Equal(this.LocalImageInfo, info);
Assert.Equal(this.LocalImageFormat, info.Format);
} }
[Fact] [Fact]
public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() public Task WhenNoMatchingFormatFoundAsync_Throws_UnknownImageFormatException()
{ {
DecoderOptions options = new() { Configuration = new() }; DecoderOptions options = new() { Configuration = new() };
var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); AsyncStreamWrapper asyncStream = new(this.DataStream, () => false);
(IImageInfo ImageInfo, IImageFormat Format) return Assert.ThrowsAsync<UnknownImageFormatException>(async () => await Image.IdentifyAsync(options, asyncStream));
info = await Image.IdentifyWithFormatAsync(options, asyncStream);
Assert.Null(info.ImageInfo);
} }
} }
} }

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

@ -5,6 +5,7 @@ using Moq;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Tests;
@ -25,13 +26,11 @@ public partial class ImageTests
protected Mock<IImageFormat> localImageFormatMock; protected Mock<IImageFormat> localImageFormatMock;
protected Mock<IImageInfo> localImageInfoMock;
protected readonly string MockFilePath = Guid.NewGuid().ToString(); protected readonly string MockFilePath = Guid.NewGuid().ToString();
internal readonly Mock<IFileSystem> LocalFileSystemMock = new Mock<IFileSystem>(); internal readonly Mock<IFileSystem> LocalFileSystemMock = new();
protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); protected readonly TestFileSystem topLevelFileSystem = new();
public Configuration LocalConfiguration { get; } public Configuration LocalConfiguration { get; }
@ -51,26 +50,29 @@ public partial class ImageTests
protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray();
protected ImageInfo LocalImageInfo { get; }
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.LocalImageInfo = new(new PixelTypeInfo(8), new(1, 1), new ImageMetadata());
this.localImageInfoMock = new Mock<IImageInfo>();
this.localImageFormatMock = new Mock<IImageFormat>(); this.localImageFormatMock = new Mock<IImageFormat>();
this.localDecoder = new Mock<IImageDecoder>(); this.localDecoder = new Mock<IImageDecoder>();
this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>())) this.localDecoder.Setup(x => x.Identify(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Returns(this.localImageInfoMock.Object); .Returns(this.LocalImageInfo);
this.localDecoder.Setup(x => x.IdentifyAsync(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) this.localDecoder.Setup(x => x.IdentifyAsync(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(this.localImageInfoMock.Object)); .Returns(Task.FromResult(this.LocalImageInfo));
this.localDecoder this.localDecoder
.Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>())) .Setup(x => x.Decode<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream>((c, s) => .Callback<DecoderOptions, Stream>((_, s) =>
{ {
using var ms = new MemoryStream(); using MemoryStream ms = new();
s.CopyTo(ms); s.CopyTo(ms);
this.DecodedData = ms.ToArray(); this.DecodedData = ms.ToArray();
}) })
@ -78,9 +80,9 @@ public partial class ImageTests
this.localDecoder this.localDecoder
.Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>())) .Setup(x => x.Decode(It.IsAny<DecoderOptions>(), It.IsAny<Stream>()))
.Callback<DecoderOptions, Stream>((c, s) => .Callback<DecoderOptions, Stream>((_, s) =>
{ {
using var ms = new MemoryStream(); using MemoryStream ms = new();
s.CopyTo(ms); s.CopyTo(ms);
this.DecodedData = ms.ToArray(); this.DecodedData = ms.ToArray();
}) })
@ -90,7 +92,7 @@ public partial class ImageTests
.Setup(x => x.DecodeAsync<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) .Setup(x => x.DecodeAsync<Rgba32>(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((_, s, _) => .Callback<DecoderOptions, Stream, CancellationToken>((_, s, _) =>
{ {
using var ms = new MemoryStream(); using MemoryStream ms = new();
s.CopyTo(ms); s.CopyTo(ms);
this.DecodedData = ms.ToArray(); this.DecodedData = ms.ToArray();
}) })
@ -100,7 +102,7 @@ public partial class ImageTests
.Setup(x => x.DecodeAsync(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>())) .Setup(x => x.DecodeAsync(It.IsAny<DecoderOptions>(), It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
.Callback<DecoderOptions, Stream, CancellationToken>((_, s, _) => .Callback<DecoderOptions, Stream, CancellationToken>((_, s, _) =>
{ {
using var ms = new MemoryStream(); using MemoryStream ms = new();
s.CopyTo(ms); s.CopyTo(ms);
this.DecodedData = ms.ToArray(); this.DecodedData = ms.ToArray();
}) })

38
tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs

@ -18,10 +18,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Rgb24>(options, this.MockFilePath); using (Image<Rgb24> img = Image.Load<Rgb24>(options, this.MockFilePath))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img); Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
}
this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration);
} }
@ -34,10 +35,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.MockFilePath); using (Image img = Image.Load(options, this.MockFilePath))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img); Assert.Equal(this.TestFormat.SampleAgnostic(), img);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }
@ -50,10 +52,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Rgba32>(options, this.MockFilePath, out IImageFormat format); using (Image<Rgba32> img = Image.Load<Rgba32>(options, this.MockFilePath))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
} }
@ -66,17 +69,18 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.MockFilePath, out IImageFormat format); using (Image img = Image.Load(options, this.MockFilePath))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }
[Fact] [Fact]
public void WhenFileNotFound_Throws() public void WhenFileNotFound_Throws()
=> Assert.Throws<System.IO.FileNotFoundException>( => Assert.Throws<FileNotFoundException>(
() => () =>
{ {
DecoderOptions options = new() DecoderOptions options = new()

15
tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -13,19 +12,19 @@ public partial class ImageTests
{ {
private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8);
private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size);
[Fact] [Fact]
public void Path_Specific() public void Path_Specific()
{ {
using var img = Image.Load<Rgba32>(this.Path); using Image<Rgba32> img = Image.Load<Rgba32>(this.Path);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Path_Agnostic() public void Path_Agnostic()
{ {
using var img = Image.Load(this.Path); using Image img = Image.Load(this.Path);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
@ -53,17 +52,17 @@ public partial class ImageTests
[Fact] [Fact]
public void Path_OutFormat_Specific() public void Path_OutFormat_Specific()
{ {
using var img = Image.Load<Rgba32>(this.Path, out IImageFormat format); using Image<Rgba32> img = Image.Load<Rgba32>(this.Path);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void Path_OutFormat_Agnostic() public void Path_OutFormat_Agnostic()
{ {
using var img = Image.Load(this.Path, out IImageFormat format); using Image img = Image.Load(this.Path);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]

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

@ -20,10 +20,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Rgb24>(options, this.ByteSpan); using (Image<Rgb24> img = Image.Load<Rgb24>(options, this.ByteSpan))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img); Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
}
this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration);
} }
@ -36,10 +37,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.ByteSpan); using (Image img = Image.Load(options, this.ByteSpan))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img); Assert.Equal(this.TestFormat.SampleAgnostic(), img);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }
@ -52,10 +54,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Bgr24>(options, this.ByteSpan, out IImageFormat format); using (Image<Bgr24> img = Image.Load<Bgr24>(options, this.ByteSpan))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifySpecificDecodeCall<Bgr24>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Bgr24>(this.Marker, this.TopLevelConfiguration);
} }
@ -68,10 +71,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.ByteSpan, out IImageFormat format); using (Image img = Image.Load(options, this.ByteSpan))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }

15
tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -15,36 +14,36 @@ public partial class ImageTests
private static Span<byte> ByteSpan => new(ByteArray); private static Span<byte> ByteSpan => new(ByteArray);
private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size);
[Fact] [Fact]
public void Bytes_Specific() public void Bytes_Specific()
{ {
using var img = Image.Load<Rgba32>(ByteSpan); using Image<Rgba32> img = Image.Load<Rgba32>(ByteSpan);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Bytes_Agnostic() public void Bytes_Agnostic()
{ {
using var img = Image.Load(ByteSpan); using Image img = Image.Load(ByteSpan);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Bytes_OutFormat_Specific() public void Bytes_OutFormat_Specific()
{ {
using var img = Image.Load<Rgba32>(ByteSpan, out IImageFormat format); using Image<Rgba32> img = Image.Load<Rgba32>(ByteSpan);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void Bytes_OutFormat_Agnostic() public void Bytes_OutFormat_Agnostic()
{ {
using var img = Image.Load(ByteSpan, out IImageFormat format); using Image img = Image.Load(ByteSpan);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
} }
} }

54
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs

@ -18,10 +18,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Rgb24>(options, this.DataStream); using (Image<Rgb24> img = Image.Load<Rgb24>(options, this.DataStream))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.Sample<Rgb24>(), img); Assert.Equal(this.TestFormat.Sample<Rgb24>(), img);
}
this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgb24>(this.Marker, this.TopLevelConfiguration);
} }
@ -34,10 +35,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.DataStream); using (Image img = Image.Load(options, this.DataStream))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat.SampleAgnostic(), img); Assert.Equal(this.TestFormat.SampleAgnostic(), img);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }
@ -50,10 +52,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var stream = new NonSeekableStream(this.DataStream); NonSeekableStream stream = new(this.DataStream);
var img = Image.Load<Rgba32>(options, stream); using (Image<Rgba32> img = Image.Load<Rgba32>(options, stream))
{
Assert.NotNull(img); Assert.NotNull(img);
}
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
} }
@ -66,10 +69,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var stream = new NonSeekableStream(this.DataStream); NonSeekableStream stream = new(this.DataStream);
Image<Rgba32> img = await Image.LoadAsync<Rgba32>(options, stream); using (Image<Rgba32> img = await Image.LoadAsync<Rgba32>(options, stream))
{
Assert.NotNull(img); Assert.NotNull(img);
}
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
} }
@ -82,10 +86,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load<Rgba32>(options, this.DataStream, out IImageFormat format); using (Image<Rgba32> img = Image.Load<Rgba32>(options, this.DataStream))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifySpecificDecodeCall<Rgba32>(this.Marker, this.TopLevelConfiguration);
} }
@ -98,10 +103,11 @@ public partial class ImageTests
Configuration = this.TopLevelConfiguration Configuration = this.TopLevelConfiguration
}; };
var img = Image.Load(options, this.DataStream, out IImageFormat format); using (Image img = Image.Load(options, this.DataStream))
{
Assert.NotNull(img); Assert.NotNull(img);
Assert.Equal(this.TestFormat, format); Assert.Equal(this.TestFormat, img.Metadata.DecodedImageFormat);
}
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
} }

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

@ -18,7 +18,7 @@ public partial class ImageTests
public void Image_Load_Throws_UnknownImageFormatException() public void Image_Load_Throws_UnknownImageFormatException()
=> Assert.Throws<UnknownImageFormatException>(() => => Assert.Throws<UnknownImageFormatException>(() =>
{ {
using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) using (Image.Load(DecoderOptions.Default, this.Stream))
{ {
} }
}); });
@ -27,7 +27,7 @@ public partial class ImageTests
public void Image_Load_T_Throws_UnknownImageFormatException() public void Image_Load_T_Throws_UnknownImageFormatException()
=> Assert.Throws<UnknownImageFormatException>(() => => Assert.Throws<UnknownImageFormatException>(() =>
{ {
using (Image.Load<Rgba32>(DecoderOptions.Default, this.Stream, out IImageFormat format)) using (Image.Load<Rgba32>(DecoderOptions.Default, this.Stream))
{ {
} }
}); });

31
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
@ -27,47 +26,47 @@ public partial class ImageTests
} }
private static void VerifyDecodedImage(Image img) private static void VerifyDecodedImage(Image img)
=> Assert.Equal(new Size(127, 64), img.Size()); => Assert.Equal(new Size(127, 64), img.Size);
[Fact] [Fact]
public void Stream_Specific() public void Stream_Specific()
{ {
using var img = Image.Load<Rgba32>(this.Stream); using Image<Rgba32> img = Image.Load<Rgba32>(this.Stream);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Stream_Agnostic() public void Stream_Agnostic()
{ {
using var img = Image.Load(this.Stream); using Image img = Image.Load(this.Stream);
VerifyDecodedImage(img); VerifyDecodedImage(img);
} }
[Fact] [Fact]
public void Stream_OutFormat_Specific() public void Stream_OutFormat_Specific()
{ {
using var img = Image.Load<Rgba32>(this.Stream, out IImageFormat format); using Image<Rgba32> img = Image.Load<Rgba32>(this.Stream);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public void Stream_OutFormat_Agnostic() public void Stream_OutFormat_Agnostic()
{ {
using var img = Image.Load(this.Stream, out IImageFormat format); using Image img = Image.Load(this.Stream);
VerifyDecodedImage(img); VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(format); Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
} }
[Fact] [Fact]
public async Task Async_Stream_OutFormat_Agnostic() public async Task Async_Stream_OutFormat_Agnostic()
{ {
this.AllowSynchronousIO = false; this.AllowSynchronousIO = false;
(Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); Image image = await Image.LoadAsync(this.Stream);
using (formattedImage.Image) using (image)
{ {
VerifyDecodedImage(formattedImage.Image); VerifyDecodedImage(image);
Assert.IsType<BmpFormat>(formattedImage.Format); Assert.IsType<BmpFormat>(image.Metadata.DecodedImageFormat);
} }
} }
@ -91,11 +90,11 @@ public partial class ImageTests
public async Task Async_Stream_OutFormat_Specific() public async Task Async_Stream_OutFormat_Specific()
{ {
this.AllowSynchronousIO = false; this.AllowSynchronousIO = false;
(Image<Rgba32> Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync<Rgba32>(this.Stream); Image<Rgba32> image = await Image.LoadAsync<Rgba32>(this.Stream);
using (formattedImage.Image) using (image)
{ {
VerifyDecodedImage(formattedImage.Image); VerifyDecodedImage(image);
Assert.IsType<BmpFormat>(formattedImage.Format); Assert.IsType<BmpFormat>(image.Metadata.DecodedImageFormat);
} }
} }

34
tests/ImageSharp.Tests/Image/ImageTests.Save.cs

@ -19,30 +19,26 @@ public partial class ImageTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "DetectedEncoding.png"); string file = Path.Combine(dir, "DetectedEncoding.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.Save(file); image.Save(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void WhenExtensionIsUnknown_Throws() public void WhenExtensionIsUnknown_Throws_UnknownImageFormatException()
{ {
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp");
Assert.Throws<NotSupportedException>( Assert.Throws<UnknownImageFormatException>(
() => () =>
{ {
using (var image = new Image<Rgba32>(10, 10)) using Image<Rgba32> image = new(10, 10);
{ image.Save(file);
image.Save(file);
}
}); });
} }
@ -52,27 +48,23 @@ public partial class ImageTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "SetEncoding.dat"); string file = Path.Combine(dir, "SetEncoding.dat");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
image.Save(file, new PngEncoder()); image.Save(file, new PngEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public void ThrowsWhenDisposed() public void ThrowsWhenDisposed()
{ {
using var image = new Image<Rgba32>(5, 5); using Image<Rgba32> image = new(5, 5);
image.Dispose(); image.Dispose();
IImageEncoder encoder = Mock.Of<IImageEncoder>(); IImageEncoder encoder = Mock.Of<IImageEncoder>();
using (var stream = new MemoryStream()) using MemoryStream stream = new();
{ Assert.Throws<ObjectDisposedException>(() => image.Save(stream, encoder));
Assert.Throws<ObjectDisposedException>(() => image.Save(stream, encoder));
}
} }
} }
} }

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

@ -21,30 +21,26 @@ public partial class ImageTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "DetectedEncodingAsync.png"); string file = Path.Combine(dir, "DetectedEncodingAsync.png");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsync(file); await image.SaveAsync(file);
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Fact] [Fact]
public async Task WhenExtensionIsUnknown_Throws() public Task WhenExtensionIsUnknown_Throws_UnknownImageFormatException()
{ {
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp");
await Assert.ThrowsAsync<NotSupportedException>( return Assert.ThrowsAsync<UnknownImageFormatException>(
async () => async () =>
{ {
using (var image = new Image<Rgba32>(10, 10)) using Image<Rgba32> image = new(10, 10);
{ await image.SaveAsync(file);
await image.SaveAsync(file);
}
}); });
} }
@ -54,15 +50,13 @@ public partial class ImageTests
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests));
string file = Path.Combine(dir, "SetEncoding.dat"); string file = Path.Combine(dir, "SetEncoding.dat");
using (var image = new Image<Rgba32>(10, 10)) using (Image<Rgba32> image = new(10, 10))
{ {
await image.SaveAsync(file, new PngEncoder()); await image.SaveAsync(file, new PngEncoder());
} }
using (Image.Load(file, out IImageFormat mime)) IImageFormat format = Image.DetectFormat(file);
{ Assert.True(format is PngFormat);
Assert.Equal("image/png", mime.DefaultMimeType);
}
} }
[Theory] [Theory]
@ -74,39 +68,29 @@ public partial class ImageTests
[InlineData("test.gif", "image/gif")] [InlineData("test.gif", "image/gif")]
public async Task SaveStreamWithMime(string filename, string mimeType) public async Task SaveStreamWithMime(string filename, string mimeType)
{ {
using (var image = new Image<Rgba32>(5, 5)) using Image<Rgba32> image = new(5, 5);
{ string ext = Path.GetExtension(filename);
string ext = Path.GetExtension(filename); image.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format);
image.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat format); Assert.Equal(mimeType, format!.DefaultMimeType);
Assert.Equal(mimeType, format!.DefaultMimeType);
using (var stream = new MemoryStream())
{
var asyncStream = new AsyncStreamWrapper(stream, () => false);
await image.SaveAsync(asyncStream, format);
stream.Position = 0; using MemoryStream stream = new();
AsyncStreamWrapper asyncStream = new(stream, () => false);
await image.SaveAsync(asyncStream, format);
(Image Image, IImageFormat Format) imf = await Image.LoadWithFormatAsync(stream); stream.Position = 0;
Assert.Equal(format, imf.Format); IImageFormat format2 = Image.DetectFormat(stream);
Assert.Equal(mimeType, imf.Format.DefaultMimeType); Assert.Equal(format, format2);
imf.Image.Dispose();
}
}
} }
[Fact] [Fact]
public async Task ThrowsWhenDisposed() public async Task ThrowsWhenDisposed()
{ {
var image = new Image<Rgba32>(5, 5); Image<Rgba32> image = new(5, 5);
image.Dispose(); image.Dispose();
IImageEncoder encoder = Mock.Of<IImageEncoder>(); IImageEncoder encoder = Mock.Of<IImageEncoder>();
using (var stream = new MemoryStream()) using MemoryStream stream = new();
{ await Assert.ThrowsAsync<ObjectDisposedException>(async () => await image.SaveAsync(stream, encoder));
await Assert.ThrowsAsync<ObjectDisposedException>(async () => await image.SaveAsync(stream, encoder));
}
} }
[Theory] [Theory]
@ -118,27 +102,23 @@ public partial class ImageTests
[InlineData("test.gif")] [InlineData("test.gif")]
public async Task SaveAsync_NeverCallsSyncMethods(string filename) public async Task SaveAsync_NeverCallsSyncMethods(string filename)
{ {
using (var image = new Image<Rgba32>(5, 5)) using Image<Rgba32> image = new(5, 5);
{ IImageEncoder encoder = image.DetectEncoder(filename);
IImageEncoder encoder = image.DetectEncoder(filename); using MemoryStream stream = new();
using (var stream = new MemoryStream()) AsyncStreamWrapper asyncStream = new(stream, () => false);
{ await image.SaveAsync(asyncStream, encoder);
var asyncStream = new AsyncStreamWrapper(stream, () => false);
await image.SaveAsync(asyncStream, encoder);
}
}
} }
[Fact] [Fact]
public async Task SaveAsync_WithNonSeekableStream_IsCancellable() public async Task SaveAsync_WithNonSeekableStream_IsCancellable()
{ {
using var image = new Image<Rgba32>(4000, 4000); using Image<Rgba32> image = new(4000, 4000);
var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; PngEncoder encoder = new() { CompressionLevel = PngCompressionLevel.BestCompression };
using var stream = new MemoryStream(); using MemoryStream stream = new();
var asyncStream = new AsyncStreamWrapper(stream, () => false); AsyncStreamWrapper asyncStream = new(stream, () => false);
var cts = new CancellationTokenSource(); CancellationTokenSource cts = new();
var pausedStream = new PausedStream(asyncStream); PausedStream pausedStream = new(asyncStream);
pausedStream.OnWaiting(s => pausedStream.OnWaiting(s =>
{ {
cts.Cancel(); cts.Cancel();

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)
{ {

72
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -23,7 +23,7 @@ public partial class ImageTests
[Fact] [Fact]
public void Width_Height() public void Width_Height()
{ {
using (var image = new Image<Rgba32>(11, 23)) using (Image<Rgba32> image = new(11, 23))
{ {
Assert.Equal(11, image.Width); Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height); Assert.Equal(23, image.Height);
@ -40,7 +40,7 @@ public partial class ImageTests
{ {
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
using (var image = new Image<Rgba32>(configuration, 11, 23)) using (Image<Rgba32> image = new(configuration, 11, 23))
{ {
Assert.Equal(11, image.Width); Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height); Assert.Equal(23, image.Height);
@ -58,7 +58,7 @@ public partial class ImageTests
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
Rgba32 color = Color.Aquamarine; Rgba32 color = Color.Aquamarine;
using (var image = new Image<Rgba32>(configuration, 11, 23, color)) using (Image<Rgba32> image = new(configuration, 11, 23, color))
{ {
Assert.Equal(11, image.Width); Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height); Assert.Equal(23, image.Height);
@ -77,9 +77,9 @@ public partial class ImageTests
byte dirtyValue = 123; byte dirtyValue = 123;
configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue);
var metadata = new ImageMetadata(); ImageMetadata metadata = new();
using (var image = Image.CreateUninitialized<L8>(configuration, 21, 22, metadata)) using (Image<L8> image = Image.CreateUninitialized<L8>(configuration, 21, 22, metadata))
{ {
Assert.Equal(21, image.Width); Assert.Equal(21, image.Width);
Assert.Equal(22, image.Height); Assert.Equal(22, image.Height);
@ -108,7 +108,7 @@ public partial class ImageTests
this.LimitBufferCapacity(100); this.LimitBufferCapacity(100);
} }
using var image = new Image<Rgba32>(this.configuration, 10, 10); using Image<Rgba32> image = new(this.configuration, 10, 10);
Rgba32 val = image[3, 4]; Rgba32 val = image[3, 4];
Assert.Equal(default(Rgba32), val); Assert.Equal(default(Rgba32), val);
image[3, 4] = Color.Red; image[3, 4] = Color.Red;
@ -116,7 +116,7 @@ public partial class ImageTests
Assert.Equal(Color.Red.ToRgba32(), val); Assert.Equal(Color.Red.ToRgba32(), val);
} }
public static TheoryData<bool, int> OutOfRangeData = new TheoryData<bool, int>() public static TheoryData<bool, int> OutOfRangeData = new()
{ {
{ false, -1 }, { false, -1 },
{ false, 10 }, { false, 10 },
@ -133,7 +133,7 @@ public partial class ImageTests
this.LimitBufferCapacity(100); this.LimitBufferCapacity(100);
} }
using var image = new Image<Rgba32>(this.configuration, 10, 10); using Image<Rgba32> image = new(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => _ = image[x, 3]); ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => _ = image[x, 3]);
Assert.Equal("x", ex.ParamName); Assert.Equal("x", ex.ParamName);
} }
@ -147,7 +147,7 @@ public partial class ImageTests
this.LimitBufferCapacity(100); this.LimitBufferCapacity(100);
} }
using var image = new Image<Rgba32>(this.configuration, 10, 10); using Image<Rgba32> image = new(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[x, 3] = default); ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[x, 3] = default);
Assert.Equal("x", ex.ParamName); Assert.Equal("x", ex.ParamName);
} }
@ -161,7 +161,7 @@ public partial class ImageTests
this.LimitBufferCapacity(100); this.LimitBufferCapacity(100);
} }
using var image = new Image<Rgba32>(this.configuration, 10, 10); using Image<Rgba32> image = new(this.configuration, 10, 10);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[3, y] = default); ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => image[3, y] = default);
Assert.Equal("y", ex.ParamName); Assert.Equal("y", ex.ParamName);
} }
@ -178,7 +178,7 @@ public partial class ImageTests
this.LimitBufferCapacity(20); this.LimitBufferCapacity(20);
} }
using var image = new Image<La16>(this.configuration, 10, 10); using Image<La16> image = new(this.configuration, 10, 10);
if (disco) if (disco)
{ {
Assert.True(image.GetPixelMemoryGroup().Count > 1); Assert.True(image.GetPixelMemoryGroup().Count > 1);
@ -204,7 +204,7 @@ public partial class ImageTests
[InlineData(true)] [InlineData(true)]
public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan)
{ {
using var image = new Image<La16>(this.configuration, 10, 10); using Image<La16> image = new(this.configuration, 10, 10);
Assert.ThrowsAny<ArgumentOutOfRangeException>(() => Assert.ThrowsAny<ArgumentOutOfRangeException>(() =>
{ {
@ -243,7 +243,7 @@ public partial class ImageTests
[Fact] [Fact]
public void NullReference_Throws() public void NullReference_Throws()
{ {
using var img = new Image<Rgb24>(1, 1); using Image<Rgb24> img = new(1, 1);
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(null)); Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(null));
@ -262,7 +262,7 @@ public partial class ImageTests
public void MultipleDisposeCalls() public void MultipleDisposeCalls()
{ {
var image = new Image<Rgba32>(this.configuration, 10, 10); Image<Rgba32> image = new(this.configuration, 10, 10);
image.Dispose(); image.Dispose();
image.Dispose(); image.Dispose();
} }
@ -270,24 +270,24 @@ public partial class ImageTests
[Fact] [Fact]
public void NonPrivateProperties_ObjectDisposedException() public void NonPrivateProperties_ObjectDisposedException()
{ {
var image = new Image<Rgba32>(this.configuration, 10, 10); Image<Rgba32> image = new(this.configuration, 10, 10);
var genericImage = (Image)image; Image genericImage = (Image)image;
image.Dispose(); image.Dispose();
// Image<TPixel> // Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => { var prop = image.Frames; }); Assert.Throws<ObjectDisposedException>(() => { ImageFrameCollection<Rgba32> prop = image.Frames; });
// Image // Image
Assert.Throws<ObjectDisposedException>(() => { var prop = genericImage.Frames; }); Assert.Throws<ObjectDisposedException>(() => { ImageFrameCollection prop = genericImage.Frames; });
} }
[Fact] [Fact]
public void Save_ObjectDisposedException() public void Save_ObjectDisposedException()
{ {
using var stream = new MemoryStream(); using MemoryStream stream = new();
var image = new Image<Rgba32>(this.configuration, 10, 10); Image<Rgba32> image = new(this.configuration, 10, 10);
var encoder = new JpegEncoder(); JpegEncoder encoder = new();
image.Dispose(); image.Dispose();
@ -307,18 +307,18 @@ public partial class ImageTests
[Fact] [Fact]
public void NonPrivateMethods_ObjectDisposedException() public void NonPrivateMethods_ObjectDisposedException()
{ {
var image = new Image<Rgba32>(this.configuration, 10, 10); Image<Rgba32> image = new(this.configuration, 10, 10);
var genericImage = (Image)image; Image genericImage = (Image)image;
image.Dispose(); image.Dispose();
// Image<TPixel> // Image<TPixel>
Assert.Throws<ObjectDisposedException>(() => { var res = image.Clone(this.configuration); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = image.Clone(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.CloneAs<Rgba32>(this.configuration); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = image.CloneAs<Rgba32>(this.configuration); });
Assert.Throws<ObjectDisposedException>(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _); }); Assert.Throws<ObjectDisposedException>(() => { bool res = image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> _); });
// Image // Image
Assert.Throws<ObjectDisposedException>(() => { var res = genericImage.CloneAs<Rgba32>(this.configuration); }); Assert.Throws<ObjectDisposedException>(() => { Image<Rgba32> res = genericImage.CloneAs<Rgba32>(this.configuration); });
} }
} }
@ -327,29 +327,29 @@ public partial class ImageTests
[Fact] [Fact]
public void KnownExtension_ReturnsEncoder() public void KnownExtension_ReturnsEncoder()
{ {
using var image = new Image<L8>(1, 1); using Image<L8> image = new(1, 1);
IImageEncoder encoder = image.DetectEncoder("dummy.png"); IImageEncoder encoder = image.DetectEncoder("dummy.png");
Assert.NotNull(encoder); Assert.NotNull(encoder);
Assert.IsType<PngEncoder>(encoder); Assert.IsType<PngEncoder>(encoder);
} }
[Fact] [Fact]
public void UnknownExtension_ThrowsNotSupportedException() public void UnknownExtension_ThrowsUnknownImageFormatException()
{ {
using var image = new Image<L8>(1, 1); using Image<L8> image = new(1, 1);
Assert.Throws<NotSupportedException>(() => image.DetectEncoder("dummy.yolo")); Assert.Throws<UnknownImageFormatException>(() => image.DetectEncoder("dummy.yolo"));
} }
[Fact] [Fact]
public void NoDetectorRegisteredForKnownExtension_ThrowsNotSupportedException() public void NoDetectorRegisteredForKnownExtension_ThrowsUnknownImageFormatException()
{ {
var configuration = new Configuration(); Configuration configuration = new();
var format = new TestFormat(); TestFormat format = new();
configuration.ImageFormatsManager.AddImageFormat(format); configuration.ImageFormatsManager.AddImageFormat(format);
configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format));
using var image = new Image<L8>(configuration, 1, 1); using Image<L8> image = new(configuration, 1, 1);
Assert.Throws<NotSupportedException>(() => image.DetectEncoder($"dummy.{format.Extension}")); Assert.Throws<UnknownImageFormatException>(() => image.DetectEncoder($"dummy.{format.Extension}"));
} }
} }
} }

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

@ -56,7 +56,7 @@ public class LargeImageIntegrationTests
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true; configuration.PreferContiguousImageBuffers = true;
configuration.ImageFormatsManager.TryFindFormatByFileExtension(formatInner, out IImageFormat format); configuration.ImageFormatsManager.TryFindFormatByFileExtension(formatInner, out IImageFormat format);
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(format!); IImageEncoder encoder = configuration.ImageFormatsManager.GetEncoder(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))

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;

24
tests/ImageSharp.Tests/ImageInfoTests.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 SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
@ -11,20 +11,20 @@ public class ImageInfoTests
[Fact] [Fact]
public void ImageInfoInitializesCorrectly() public void ImageInfoInitializesCorrectly()
{ {
const int Width = 50; const int width = 50;
const int Height = 60; const int height = 60;
var size = new Size(Width, Height); Size size = new(width, height);
var rectangle = new Rectangle(0, 0, Width, Height); Rectangle rectangle = new(0, 0, width, height);
var pixelType = new PixelTypeInfo(8); PixelTypeInfo pixelType = new(8);
var meta = new ImageMetadata(); ImageMetadata meta = new();
var info = new ImageInfo(pixelType, Width, Height, meta); ImageInfo info = new(pixelType, width, height, meta);
Assert.Equal(pixelType, info.PixelType); Assert.Equal(pixelType, info.PixelType);
Assert.Equal(Width, info.Width); Assert.Equal(width, info.Width);
Assert.Equal(Height, info.Height); Assert.Equal(height, info.Height);
Assert.Equal(size, info.Size()); Assert.Equal(size, info.Size);
Assert.Equal(rectangle, info.Bounds()); Assert.Equal(rectangle, info.Bounds);
Assert.Equal(meta, info.Metadata); Assert.Equal(meta, info.Metadata);
} }
} }

2
tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs

@ -15,7 +15,7 @@ public abstract class BaseImageOperationsExtensionTest : IDisposable
protected readonly GraphicsOptions options; protected readonly GraphicsOptions options;
private readonly Image<Rgba32> source; private readonly Image<Rgba32> source;
public Rectangle SourceBounds() => this.source.Bounds(); public Rectangle SourceBounds() => this.source.Bounds;
public BaseImageOperationsExtensionTest() public BaseImageOperationsExtensionTest()
{ {

2
tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs

@ -64,7 +64,7 @@ internal class FakeImageOperationsProvider : IImageProcessingContextFactory
public Size GetCurrentSize() public Size GetCurrentSize()
{ {
return this.Source.Size(); return this.Source.Size;
} }
public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save